(一)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的好处是其作了不少全面的异常处理,使得调用端能够直接使用而不用本身实现多种异常的处理。
(二)深浅拷贝
(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)
深拷贝就是数据完彻底全独立拷贝出来一份,包含对象里面的自对象的拷贝,因此原始对象的改变不会形成深拷贝里任何子元素的改变