生成器
文章摘自:
https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/
https://www.cnblogs.com/wj-1314/p/8490822.html
您可能听说过,带有 yield 的函数在 Python 中被称之为 generator(生成器),何谓 generator ?
我们先抛开 generator,以一个常见的编程题目来展示 yield 的概念。
如何生成斐波那契数列
斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到。用计算机程序输出斐波那契數列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数:
清单 1. 简单输出斐波那契數列前 N 个数
1 def fab(max): 2 n, a, b = 0, 0, 1 3 while n < max: 4 print b 5 a, b = b, a + b 6 n = n + 1
执行 fab(5),我们可以得到如下输出:
1 >>>fab(5) 2 1 3 1 4 2 5 3 6 5
结果没有问题,但有经验的开发者会指出,直接在 fab 函数中用 print 打印数字会导致该函数可复用性较差,因为 fab 函数返回 None(return,return None,不写return),其他函数无法获得该函数生成的数列。 要提高 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。以下是 fab 函数改写后的第二个版本:
清单 2. 输出斐波那契數列前 N 个数第二版
1 def fab(max): 2 n, a, b = 0, 0, 1 3 L = [] 4 while n < max: 5 L.append(b) 6 a, b = b, a + b 7 n = n + 1 8 return L
可以使用如下方式打印出 fab 函数返回的 List:
1 >>> for n in fab(5): 2 ... print n 3 ... 4 1 5 1 6 2 7 3 8 5
改写后的 fab 函数通过返回 List 能满足复用性的要求,但是更有经验的开发者会指出,该函数在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,最好不要用 List来保存中间结果,而是通过 iterable 对象来迭代。例如,在 Python2.x 中,代码:
清单3.通过 iterable 对象来迭代
1 for i in range(1000): pass
会导致生成一个 1000 个元素的 List,而代码:
1 for i in xrange(1000): pass
则不会生成一个 1000 个元素的 List,而是在每次迭代中返回下一个数值,内存空间占用很小。因为 xrange 不返回 List,而是返回一个 iterable 对象。
利用 iterable 我们可以把 fab 函数改写为一个支持 iterable 的 class,以下是第三个版本的 Fab:
清单 4. 第三个版本
1 class Fab(object): 2 3 def __init__(self, max): 4 self.max = max 5 self.n, self.a, self.b = 0, 0, 1 6 7 def __iter__(self): 8 return self 9 10 def next(self): 11 if self.n < self.max: 12 r = self.b 13 self.a, self.b = self.b, self.a + self.b 14 self.n = self.n + 1 15 return r 16 raise StopIteration()
Fab 类通过 next() 不断返回数列的下一个数,内存占用始终为常数
1 >>> for n in Fab(5): 2 ... print n 3 ... 4 1 5 1 6 2 7 3 8 5
然而,使用 class 改写的这个版本,代码远远没有第一版的 fab 函数来得简洁。如果我们想要保持第一版 fab 函数的简洁性,同时又要获得 iterable 的效果,yield 就派上用场了:
清单 5. 使用 yield 的第四版
1 def fab(max): 2 n, a, b = 0, 0, 1 3 while n < max: 4 yield b 5 # print b 6 a, b = b, a + b 7 n = n + 1 8 9 '''
第四个版本的 fab 和第一版相比,仅仅把 print b 改为了 yield b,就在保持简洁性的同时获得了 iterable 的效果。
调用第四版的 fab 和第二版的 fab 完全一致
1 >>> for n in fab(5): 2 ... print n 3 ... 4 1 5 1 6 2 7 3
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:
清单 6. 执行流程
1 >>> f = fab(5) 2 >>> f.next() 3 1 4 >>> f.next() 5 1 6 >>> f.next() 7 2 8 >>> f.next() 9 3 10 >>> f.next() 11 5 12 >>> f.next() 13 Traceback (most recent call last): 14 File "<stdin>", line 1, in <module> 15 StopIteration
当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。
我们可以得出以下结论:
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:
清单 7. 使用 isgeneratorfunction 判断
1 >>> from inspect import isgeneratorfunction 2 >>> isgeneratorfunction(fab) 3 True
要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:
清单 8. 类的定义和类的实例
1 >>> import types 2 >>> isinstance(fab, types.GeneratorType) 3 False 4 >>> isinstance(fab(5), types.GeneratorType) 5 True
fab 是无法迭代的,而 fab(5) 是可迭代的:
1 >>> from collections import Iterable 2 >>> isinstance(fab, Iterable) 3 False 4 >>> isinstance(fab(5), Iterable) 5 True
每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:
1 >>> f1 = fab(3) 2 >>> f2 = fab(5) 3 >>> print 'f1:', f1.next() 4 f1: 1 5 >>> print 'f2:', f2.next() 6 f2: 1 7 >>> print 'f1:', f1.next() 8 f1: 1 9 >>> print 'f2:', f2.next() 10 f2: 1 11 >>> print 'f1:', f1.next() 12 f1: 2 13 >>> print 'f2:', f2.next() 14 f2: 2 15 >>> print 'f2:', f2.next() 16 f2: 3 17 >>> print 'f2:', f2.next() 18 f2: 5
return 的作用
在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
另一个例子
另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:
清单 9. 另一个 yield 的例子
1 def read_file(fpath): 2 BLOCK_SIZE = 1024 3 with open(fpath, 'rb') as f: 4 while True: 5 block = f.read(BLOCK_SIZE) 6 if block: 7 yield block 8 else: 9 return
以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。
注:本文的代码均在 Python 2.7 中调试通过
什么是生成器?
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器
python中的生成器
要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator
1 #列表生成式 2 lis = [x*x for x in range(10)] 3 print(lis) 4 #生成器 5 generator_ex = (x*x for x in range(10)) 6 print(generator_ex) 7 8 结果: 9 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 10 <generator object <genexpr> at 0x000002A4CBF9EBA0>
那么创建lis和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object <genexpr> at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?
如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:
1 #生成器 2 generator_ex = (x*x for x in range(10)) 3 print(next(generator_ex)) 4 print(next(generator_ex)) 5 print(next(generator_ex)) 6 print(next(generator_ex)) 7 print(next(generator_ex)) 8 print(next(generator_ex)) 9 print(next(generator_ex)) 10 print(next(generator_ex)) 11 print(next(generator_ex)) 12 print(next(generator_ex)) 13 print(next(generator_ex)) 14 结果: 15 0 16 1 17 4 18 9 19 16 20 25 21 36 22 49 23 64 24 81 25 Traceback (most recent call last): 26 27 File "列表生成式.py", line 42, in <module> 28 29 print(next(generator_ex)) 30 31 StopIteration
可以看到,generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:
1 #生成器 2 generator_ex = (x*x for x in range(10)) 3 for i in generator_ex: 4 print(i) 5 6 结果: 7 0 8 1 9 4 10 9 11 16 12 25 13 36 14 49 15 64 16 81
所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如著名的斐波那契数列,除第一个和第二个数外,任何一个数都可以由前两个相加得到:
1,1,2,3,5,8,12,21,34.....
斐波那契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
1 #fibonacci数列 2 def fib(max): 3 n,a,b =0,0,1 4 while n < max: 5 a,b =b,a+b 6 n = n+1 7 print(a) 8 return 'done' 9 10 a = fib(10) 11 print(fib(10))
fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说上面的函数也可以用generator来实现,上面我们发现,print(b)每次函数运行都要打印,占内存,所以为了不占内存,我们也可以使用生成器,这里叫yield。如下:
1 def fib(max): 2 n,a,b =0,0,1 3 while n < max: 4 yield b 5 a,b =b,a+b 6 n = n+1 7 return 'done' 8 9 a = fib(10) 10 print(fib(10))
但是返回的不再是一个值,而是一个生成器:
1 <generator object fib at 0x000001C03AC34FC0>
那么这样就不占内存了,这里说一下generator和函数的执行流程,函数是顺序执行的,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时,从上次的返回yield语句处执行,也就是用多少,取多少,不占内存。
1 def fib(max): 2 n,a,b =0,0,1 3 while n < max: 4 yield b 5 a,b =b,a+b 6 n = n+1 7 return 'done' 8 9 a = fib(10) 10 print(fib(10)) 11 print(a.__next__()) 12 print(a.__next__()) 13 print(a.__next__()) 14 print("可以顺便干其他事情") 15 print(a.__next__()) 16 print(a.__next__()) 17 18 结果: 19 <generator object fib at 0x0000023A21A34FC0> 20 1 21 1 22 2 23 可以顺便干其他事情 24 3 25 5
在上面fib的例子,我们在循环过程中不断调用yield
,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用next()
来获取下一个返回值,而是直接使用for
循环来迭代:
1 def fib(max): 2 n,a,b =0,0,1 3 while n < max: 4 yield b 5 a,b =b,a+b 6 n = n+1 7 return 'done' 8 for i in fib(6): 9 print(i) 10 11 结果: 12 1 13 1 14 2 15 3 16 5 17 8
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果拿不到返回值,那么就会报错,所以为了不让报错,就要进行异常处理,拿到返回值,如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
1 def fib(max): 2 n,a,b =0,0,1 3 while n < max: 4 yield b 5 a,b =b,a+b 6 n = n+1 7 return 'done' 8 g = fib(6) 9 while True: 10 try: 11 x = next(g) 12 print('generator: ',x) 13 except StopIteration as e: 14 print("生成器返回值:",e.value) 15 break 16 17 18 结果: 19 generator: 1 20 generator: 1 21 generator: 2 22 generator: 3 23 generator: 5 24 generator: 8 25 生成器返回值: done
还可以通过yield实现在单线程的情况下实现并发运算的效果
1 import time 2 3 4 def consumer(name): 5 print("%s 准备学习啦!" % name) 6 while True: 7 lesson = yield 8 9 print("开始[%s]了,[%s]老师来讲课了!" % (lesson, name)) 10 11 12 def producer(): 13 c = consumer('A') 14 print(c) 15 c2 = consumer('B') 16 print(c2) 17 a1 = c.__next__() 18 print(a1) 19 a2 = c2.__next__() 20 print(a2) 21 print("同学们开始上课 了!") 22 for i in range(10): 23 time.sleep(1) 24 print("到了两个同学!") 25 c.send(i) 26 c2.send(i) 27 producer() 28 29 结果: 30 <generator object consumer at 0x0000025046DE37C8> 31 <generator object consumer at 0x000002504FBF6228> 32 A 准备学习啦! 33 None 34 B 准备学习啦! 35 None 36 同学们开始上课 了! 37 到了两个同学! 38 开始[0]了,[A]老师来讲课了! 39 开始[0]了,[B]老师来讲课了! 40 到了两个同学! 41 开始[1]了,[A]老师来讲课了! 42 开始[1]了,[B]老师来讲课了! 43 到了两个同学! 44 开始[2]了,[A]老师来讲课了! 45 开始[2]了,[B]老师来讲课了! 46 到了两个同学! 47 开始[3]了,[A]老师来讲课了! 48 开始[3]了,[B]老师来讲课了! 49 到了两个同学! 50 开始[4]了,[A]老师来讲课了! 51 开始[4]了,[B]老师来讲课了! 52 到了两个同学! 53 开始[5]了,[A]老师来讲课了! 54 开始[5]了,[B]老师来讲课了! 55 到了两个同学! 56 开始[6]了,[A]老师来讲课了! 57 开始[6]了,[B]老师来讲课了! 58 到了两个同学! 59 开始[7]了,[A]老师来讲课了! 60 开始[7]了,[B]老师来讲课了! 61 到了两个同学! 62 开始[8]了,[A]老师来讲课了! 63 开始[8]了,[B]老师来讲课了! 64 到了两个同学! 65 开始[9]了,[A]老师来讲课了! 66 开始[9]了,[B]老师来讲课了!
由上面的例子我么可以发现,python提供了两种基本的方式
生成器函数:也是用def定义的,利用关键字yield一次性返回一个结果,阻塞,重新开始
生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果
——生成器函数
为什么叫生成器函数?因为它随着时间的推移生成了一个数值队列。一般的函数在执行完毕之后会返回一个值然后退出,但是生成器函数会自动挂起,然后重新拾起急需执行,他会利用yield关键字关起函数,给调用者返回一个值,同时保留了当前的足够多的状态,可以使函数继续执行,生成器和迭代协议是密切相关的,迭代器都有一个__next__()__成员方法,这个方法要么返回迭代的下一项,要买引起异常结束迭代。
1 # 函数有了yield之后,函数名+()就变成了生成器 2 # return在生成器中代表生成器的中止,直接报错 3 # next的作用是唤醒并继续执行 4 # send的作用是唤醒并继续执行,发送一个信息到生成器内部 5 '''生成器''' 6 7 def create_counter(n): 8 print("create_counter") 9 while True: 10 yield n 11 print("increment n") 12 n +=1 13 14 gen = create_counter(2) 15 print(gen) 16 print(next(gen)) 17 print(next(gen)) 18 19 结果: 20 <generator object create_counter at 0x0000023A1694A938> 21 create_counter 22 2 23 increment n 24 3 25 Process finished with exit code
——生成器表达式
生成器表达式来源于迭代和列表解析的组合,生成器和列表解析类似,但是它使用尖括号而不是方括号
1 >>> # 列表解析生成列表 2 >>> [ x ** 3 for x in range(5)] 3 [0, 1, 8, 27, 64] 4 >>> 5 >>> # 生成器表达式 6 >>> (x ** 3 for x in range(5)) 7 <generator object <genexpr> at 0x000000000315F678> 8 >>> # 两者之间转换 9 >>> list(x ** 3 for x in range(5)) 10 [0, 1, 8, 27, 64]
一个迭代既可以被写成生成器函数,也可以被协程生成器表达式,均支持自动和手动迭代。而且这些生成器只支持一个active迭代,也就是说生成器的迭代器就是生成器本身。
迭代器(迭代就是循环)
迭代器包含有next方法的实现,在正确的范围内返回期待的数据以及超出范围后能够抛出StopIteration的错误停止迭代。
我们已经知道,可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list,tuple,dict,set,str等
一类是generator,包括生成器和带yield的generator function
这些可以直接作用于for 循环的对象统称为可迭代对象:Iterable
可以使用isinstance()判断一个对象是否为可Iterable对象
1 >>> from collections import Iterable 2 >>> isinstance([], Iterable) 3 True 4 >>> isinstance({}, Iterable) 5 True 6 >>> isinstance('abc', Iterable) 7 True 8 >>> isinstance((x for x in range(10)), Iterable) 9 True 10 >>> isinstance(100, Iterable) 11 False
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
所以这里讲一下迭代器
一个实现了iter方法的对象时可迭代的,一个实现next方法的对象是迭代器
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
1 >>> from collections import Iterator 2 >>> isinstance((x for x in range(10)), Iterator) 3 True 4 >>> isinstance([], Iterator) 5 False 6 >>> isinstance({}, Iterator) 7 False 8 >>> isinstance('abc', Iterator) 9 False
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable(可迭代对象)
,却不是Iterator(迭代器)
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True
你可能会问,为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
判断下列数据类型是可迭代对象or迭代器
1 s='hello' 2 l=[1,2,3,4] 3 t=(1,2,3) 4 d={'a':1} 5 set={1,2,3} 6 f=open('a.txt')
1 s='hello' #字符串是可迭代对象,但不是迭代器 2 l=[1,2,3,4] #列表是可迭代对象,但不是迭代器 3 t=(1,2,3) #元组是可迭代对象,但不是迭代器 4 d={'a':1} #字典是可迭代对象,但不是迭代器 5 set={1,2,3} #集合是可迭代对象,但不是迭代器 6 # ************************************* 7 f=open('test.txt') #文件是可迭代对象,是迭代器 8 9 #如何判断是可迭代对象,只有__iter__方法,执行该方法得到的迭代器对象。 10 # 及可迭代对象通过__iter__转成迭代器对象 11 from collections import Iterator #迭代器 12 from collections import Iterable #可迭代对象 13 14 print(isinstance(s,Iterator)) #判断是不是迭代器 15 print(isinstance(s,Iterable)) #判断是不是可迭代对象 16 17 #把可迭代对象转换为迭代器 18 print(isinstance(iter(s),Iterator))
注意:文件的判断
1 f = open('housing.csv') 2 from collections import Iterator 3 from collections import Iterable 4 5 print(isinstance(f,Iterator)) 6 print(isinstance(f,Iterable)) 7 8 True 9 True
结论:文件是可迭代对象,也是迭代器
小结:
- 凡是可作用于
for
循环的对象都是Iterable
类型; - 凡是可作用于
next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列; - 集合数据类型如
list
、dict
、str
等是Iterable
但不是Iterator(只含有__iter__()不含__next__())
,不过可以通过iter()
函数获得一个Iterator
对象。
Python3的for
循环本质上就是通过不断调用next()
函数实现的,例如:
1 for x in [1, 2, 3, 4, 5]: 2 pass
实际上完全等价于:
1 # 首先获得Iterator对象: 2 it = iter([1, 2, 3, 4, 5]) 3 # 循环: 4 while True: 5 try: 6 # 获得下一个值: 7 x = next(it) 8 except StopIteration: 9 # 遇到StopIteration就退出循环 10 break
对yield的总结
(1)通常的for..in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。他可以是a = [1,2,3],也可以是a = [x*x for x in range(3)]。
它的缺点也很明显,就是所有数据都在内存里面,如果有海量的数据,将会非常耗内存。
(2)生成器是可以迭代的,但是只可以读取它一次。因为用的时候才生成,比如a = (x*x for x in range(3))。!!!!注意这里是小括号而不是方括号。
(3)生成器(generator)能够迭代的关键是他有next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。
(4)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代
(5)yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行
(6)yield就是return返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始。
(7)带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。
(8)send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。
(9)send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。
(10)第一次调用时候必须先next()或send(),否则会报错,send后之所以为None是因为这时候没有上一个yield,所以也可以认为next()等同于send(None)