Python生成器
生成器 通过列表生成式,我们可以直接创建一个列表。但是受内存限制,列表容量肯定是有限的。 而且,创建一个很多很多元素的列表,不仅占用很大的内存空间,如果我们仅仅访问前几 元素,那后面绝大多数的元素占用的空间就白白浪费了。 所以,如果列表元素可以按照某种算法推算出来,那多好!这样就不必浪费空间了,这样 你好我好大家好。 在Python中,这种一边循环一边计算的机制,称为生成器:generator 要创建一个generator,有很多种方法。 1)只要把一个列表生成式的[]改为(),就创建了一个generator。 L = [i * i for i in range(5)] print(L)#[0, 1, 4, 9, 16] g = (i * i for i in range(5)) print(g)#<generator object <genexpr> at 0x111136ca8> 创建L和g的区别仅在于最外层[]和(),L是一个list而g是一个genratot。 我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢? 我们可以通过 next(g)挨个拿其中的元素,最后一个取完之后再进行 next()操作会 报错(StopIteration)。 一个一个取是在太繁琐,我们可以用for循环,因为genertor是一个可迭代对象。 g = (i * i for i in range(5)) for i in g: print(i) #用for循环来迭代,并且不需要关心StopIteration报错。 2)用函数来实现。 比如,斐波那契数列:1,1,2,3,5,8,13,21,34.... 用列表生成式写不出来,但是我们可以用函数把它打印出来: def fib(number): n, a, b = 0, 0, 1 while n < number: print(b) a, b = b, a + b n = n + 1 return 'OK!' print(fib(5)) 结果: 1 1 2 3 5 OK! 我们可以看出从第一个元素开始,推算出后续任意的元素。很像generator。 要把fib函数变成generator,只需要把 print(b)改为 yield b就可以了: def fib(number): n, a, b = 0, 0, 1 while n < number: yield b a, b = b, a + b n = n + 1 return 'OK!' print(fib(5))#<generator object fib at 0x105606ca8> 注意: 这里难理解的就是generator和函数的执行流程是不一样的。 函数是顺序执行,遇到return语句或者最后一行函数语句就 返回。 注意:函数创建一次生成一个生成器,所以我们会将创建的生 成器赋值给一个变量。如果直接用函数本身这个生成器, 我们没用一次生成一个新的生成器对象,所以,我们一 般都将创建的生成器赋给一个变量。 generetor的函数,在每次调用 next()的时候执行,遇到 yield语句返回,再次执行时从上次返回的yield语句处继续 执行。 例子: def odd(): print('step 1') yield 1 print('step 2') yield(3) print('step 3') yield(5) f = odd() print(next(f)) print(next(f)) print(next(f)) 结果: step 1 1 step 2 3 step 3 5 执行三次后再执行 next(f) 就会报错了。 可以看到odd不是普通函数,而是generator遇到yield就会中断 下次又继续执行,执行三次后已经没有yield可以执行,所以再执 行 next(f) 就会报错了。最后的yield后面一般不写东西。 把函数改为generator后,我们基本不这么用 next()来获取下一个返回值 而是直接使用for循环来迭代。 比如上面的fib函数。 def fib(number): n, a, b = 0, 0, 1 while n < number: yield b a, b = b, a + b n = n + 1 return 'OK!' for i in fib(5): print(i) 结果: 1 1 2 3 5 注意:用for循环调用generator时,发现拿不到generator的return 语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误 返回值包含在StopIteration的value中。 (关于如何捕获,异常处理见。) 接下来我们来看send⽅方法, send和next()一样都可以让生成器执行到下一个yield. 例子; def eat(): print("我吃什么啊") a = yield "馒头" print("a=",a) b = yield "⼤大饼" print("b=",b) c = yield "韭菜盒⼦子" print("c=",c) yield "GAME OVER" gen = eat() # 获取⽣生成器器 ret1 = next(gen) print(ret1) ret2 = gen.send("胡辣汤") print(ret2) ret3 = gen.send("狗粮") print(ret3) ret4 = gen.send("猫粮") print(ret4) 结果: 我吃什么啊 馒头 a= 胡辣汤 ⼤大饼 b= 狗粮 韭菜盒⼦子 c= 猫粮 GAME OVER send和next()区别: 1. send()和next()都是让生成器向下走一次 2. send可以给上一个yield的位置传递值,不能给最后一个yield发送值. 在第一次执行生成器代码的时候不能使用send() 三道题: <1> def add(a, b): return a+b def Test(): for i in range(4): yield i g = Test() for n in [2, 10]: g = (add(n, i) for i in g) print(list(g)) 结果: [20, 21, 22, 23] 分析: print(list(g))这个语句之前都是准备阶段,只程序走到 print(list(g))这个语句这里程序才会执行,那么在此程 序执行的准备阶段前面做了什么事呢: 到for循环这 n = 2 g = (add(n, i) for i in Test()) n = 10 g = (add(n, i) for i in add(n, i) for i in Test()) 这个时候执行list(g)其实执行的只是n==10的时候 结果:[20, 21, 22, 23] <2> def func(): print('1') yield 'This is one step' print('2') yield 'This is two step' print('3') yield 'This is theree step' it = func()#函数返回生成器 print(list(it)) 结果: 1 2 3 ['This is one step', 'This is two step', 'This is theree step'] <3> def func(): print(111) yield 222 g = func() # 生成器g g1 = (i for i in g) # 生成器g1. 但是g1的数据来源于g g2 = (i for i in g1) # 生成器器g2. 来源g1 print(list(g)) # 获取g中的数据. 这时func()才会被执行. 打印111.获取到222. g完毕. print(list(g1)) # 获取g1中的数据. g1的数据来源是g. 但是g已经取完了了. g1 也就没有数据了 print(list(g2)) # 和g1同理 结果: 111 [222] [] [] Form zero to hero