• python yield,yield from,深浅拷贝


    (一)yield和yield from

    转自:理解yield   yield from

    (1)yield

    1、通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。它可以是mylist = [1, 2, 3],也可以是mylist = [x*x for x in range(3)]。 它的缺陷是所有数据都在内存中,如果有海量数据的话将会非常耗内存。

    2、对比可迭代对象,迭代器其实就只是多了一个函数:__next__(),可以不再使用for循环来间断获取元素值,而可以直接使用next()方法来实现,可通过iter(a),将可迭代对象a转换为一个迭代器。

    3、生成器是可以迭代的,但只可以读取它一次。因为用的时候才生成。比如 mygenerator = (x*x for x in range(3)),注意这里用到了(),它就不是数组,而上面的例子是[]。可迭代对象和迭代器,是将所有的值都生成存放在内存中,而生成器则是需要元素才临时生成,节省时间,节省空间。

    4、带有 yield 的函数不再是一个普通函数,而是一个生成器generator,可用于迭代。yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回yield后面的值。并在这里阻塞,等待下一次的调用。从而实现节省内存,实现异步编程。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码开始执行。即yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始。

    5、生成器并不是一次生成所有元素,而是一次一次的执行返回,激活生成器执行的方法有:使用next(),或者使用generator.send(None)。next()等同于send(None),for循环就用到了next()

    6、带有yield的函数不仅仅只用于for循环中,而且可用于某个函数的参数,只要这个函数的参数允许迭代参数。比如array.extend函数,它的原型是array.extend(iterable)。

    7、send(msg)与next()的区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值。换句话说,就是send可以强行修改上一个yield表达式值。比如函数中有一个yield赋值,a = yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用.send(10),那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10。

    8、send(msg)与next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数。

    9、第一次调用时必须先next()或send(None),否则会报错,send后之所以为None是因为这时候没有上一个yield。可以认为,next()等同于send(None),for循环就用到了next()。

    (2)yield from

    多线程与协程:使用多线程实现并发时,多线程的运行需要频繁的加锁解锁,切换线程,这极大地降低了并发性能;协程是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序。协程和线程,有相似点,多个协程之间和线程一样,只会交叉串行执行;也有不同点,线程之间要频繁进行切换,加锁解锁,复杂度高且效率低。协程通过使用 yield 暂停生成器,可以将程序的执行流程交给其他的子程序,从而实现不同子程序的之间的交替执行,即程序从yield处暂停,然后可以返回去做别的事。

    yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器

    yield from后面加上可迭代对象,可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰(这种情况下使用yield需要两个循环,而yield from只需要一个循环)。

    当 yield from 后面加上一个生成器后,就实现了生成的嵌套。实现生成器的嵌套,并不是一定必须要使用yield from,但使用yield from可以避免自己处理各种料想不到的异常。生成器嵌套的几个概念:

    1、调用方:调用委托生成器的客户端(调用方)代码

    2、委托生成器:包含yield from表达式的生成器函数

    3、子生成器:yield from后面加的生成器函数

    这几个概念的示例如下:

    # 子生成器
    def average_gen():
        total = 0
        count = 0
        average = 0
        while True:
            new_num = yield average
            count += 1
            total += new_num
            average = total/count
    
    # 委托生成器
    def proxy_gen():
        while True:
            yield from average_gen()
    
    # 调用方
    def main():
        calc_average = proxy_gen()
        next(calc_average)            # 预激下生成器
        print(calc_average.send(10))  # 打印:10.0
        print(calc_average.send(20))  # 打印:15.0
        print(calc_average.send(30))  # 打印:20.0
    
    if __name__ == '__main__':
        main()

    委托生成器的作用是:在调用方与子生成器之间建立一个双向通道即调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。委托生成器只起一个桥梁作用,它建立的是一个双向通道,它并不会对子生成器yield回来的内容做拦截。如下:

    # 子生成器
    def average_gen():
        total = 0
        count = 0
        average = 0
        while True:
            new_num = yield average
            if new_num is None:
                break
            count += 1
            total += new_num
            average = total/count
    
        # 每一次return,都意味着当前协程结束。
        return total,count,average
    
    # 委托生成器
    def proxy_gen():
        while True:
            # 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行
            total, count, average = yield from average_gen()
            print("计算完毕!!
    总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
    
    # 调用方
    def main():
        calc_average = proxy_gen()
        next(calc_average)            # 预激协程
        print(calc_average.send(10))  # 打印:10.0
        print(calc_average.send(20))  # 打印:15.0
        print(calc_average.send(30))  # 打印:20.0
        calc_average.send(None)      # 结束协程
        # 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程
    
    if __name__ == '__main__':
        main()
    
    
    程序输出:
    10.0
    15.0
    20.0
    计算完毕!!
    总共传入 3 个数值, 总和:60,平均数:20.0

    yield from的好处是其做了很多全面的异常处理,使得调用端可以直接使用而不用自己实现多种异常的处理。

    (二)深浅拷贝

    转自 Python拷贝    深浅拷贝

    (1)=赋值:数据完全共享(=赋值是在内存中指向同一个对象,如果是可变(mutable)类型,比如列表,修改其中一个,另一个必定改变

    如果是不可变类型(immutable),比如字符串,修改了其中一个,另一个并不会变

    l2 = l1 ,l1 完全赋值给l2 ,l2的内存地址与l1 相同,即内存完全指向

    l1 = [1, 2, 3, ['aa', 'bb']]
    l2 = l1
    l2[0]='aaa'
    l2[3][0]='bbb'
    print(l1)  #['aaa', 2, 3, ['bbb', 'bb']]
    print(id(l1)==id(l2))  #True

    (2)浅拷贝:数据半共享(复制其数据独立内存存放,但是只拷贝成功第一层)没有拷贝子对象,所以原始数据改变,子对象会改变

    l1 = [1,2,3,[11,22,33]]
    l2 = l1.copy()
    print(l2) #[1,2,3,[11,22,33]]
    l2[3][2]='aaa'
    print(l1) #[1, 2, 3, [11, 22, 'aaa']]
    print(l2) #[1, 2, 3, [11, 22, 'aaa']]
    l1[0]= 0
    print(l1) #[0, 2, 3, [11, 22, 'aaa']]
    print(l2) #[1, 2, 3, [11, 22, 'aaa']]
    print(id(l1)==id(l2)) #Flase

    l2浅拷贝了l1 ,之后l2把其列表中的列表的元素给修改,从结果看出,l1也被修改了。但是仅仅修改l1列表中的第一层元素,却并没有影响l2。

    比较一下l2与l1的内存地址:False,说明,l2在内存中已经独立出一部分复制了l1的数据,但是只是浅拷贝,第二层的数据并没有拷贝成功,而是指向了l1中的第二层数据的内存地址,所以共享内存‘相当于‘’等号赋值’‘,所以就会有l2中第二层数据发生变化,l1中第二层数据也发生变化

    l2拷贝l1的时候只拷贝了他的第一层,也就是在其他内存中重新创建了l1的第一层数据,但是l2无法拷贝l1的第二层数据,也就是列表中的列表,所以他就只能指向l1中的第二层数据

    由此,当修改l1中第二层数据的时候,浅拷贝l1的l2中的第二层数据也随之发生改变

    (3)深拷贝:数据完全不共享(复制其数据完完全全放独立的一个内存,完全拷贝,数据不共享)

    深拷贝就是完完全全复制了一份,且数据不会互相影响,因为内存不共享。

    深拷贝的方法有:导入模块

    import copy
    l1 = [1, 2, 3, [11, 22, 33]]
    # l2 = copy.copy(l1)  浅拷贝
    l2 = copy.deepcopy(l1)
    print(l1,'>>>',l2)
    l2[3][0] = 1111
    print(l1,">>>",l2)

    深拷贝就是数据完完全全独立拷贝出来一份,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变

     

  • 相关阅读:
    我和LEGO Mindstroms NXT(1)
    [翻译]介绍Xbox LIVE社区游戏
    想做一个显示全国火车运行图的网站(1)想想
    [翻译]CCR and DSS Toolkit 2008 发布了
    我和LEGO Mindstroms NXT(2)
    [翻译]机器人游戏可以下载了!
    vs的form标签引起css走样问题
    做个md5查询站(1)从8年前说开去
    [翻译]Microsoft Robotics Developer Sutdio 2008 7月CTP发布了
    做个md5查询站(3)数据格式
  • 原文地址:https://www.cnblogs.com/ccxikka/p/10658200.html
Copyright © 2020-2023  润新知