python yield,yield from,深浅拷贝

(一)yield和yield fromhtml

转自:理解yield   yield from编程

 

(1)yield数组

一、一般的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象,相似的还有链表,字符串,文件。它能够是mylist = [1, 2, 3],也能够是mylist = [x*x for x in range(3)]。 它的缺陷是全部数据都在内存中,若是有海量数据的话将会很是耗内存。多线程

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

三、生成器是能够迭代的,但只能够读取它一次。由于用的时候才生成。好比 mygenerator = (x*x for x in range(3)),注意这里用到了(),它就不是数组,而上面的例子是[]。可迭代对象和迭代器,是将全部的值都生成存放在内存中,而生成器则是须要元素才临时生成,节省时间,节省空间。异步

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

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

六、带有yield的函数不只仅只用于for循环中,并且可用于某个函数的参数,只要这个函数的参数容许迭代参数。好比array.extend函数,它的原型是array.extend(iterable)。性能

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

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

九、第一次调用时必须先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能够避免本身处理各类料想不到的异常。生成器嵌套的几个概念:

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

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

三、子生成器: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("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".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)

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

相关文章
相关标签/搜索