注意:生成器(Generator)的概念稍微有点乱(在不同的上下文可能有不同的指向):
- 指代 生成器函数(Generator function):包含yield的函数
- 指代 生成器迭代器(Generator iterator):包含yield的函数的返回值
或许可以这样说:?
- 开始时,生成器函数(文档中叫做生成器),用于生成一个迭代器(就返回一个普通迭代器)。
- 随着功能增强,生成的这个迭代器变得比普通迭代器更厉害(有了新的名字:生成器迭代器)
- 生成器迭代器在代码中也叫做生成器
生成器(Generator Iterator)是迭代器(Iterator)的一种。它非常类似返回数组的函数——具有参数、可被调用并产生一系列的值,但是它不是构造出所有数据并一次返回,而是每次产生一个值。
生成器和yield最早于1975年出现在CLU语言中,直到2001年才出现在Python2.2中。即便如此,它在Python中也已经20多年了。
那么,面对对这个熟悉的陌生人...
从哪儿开始?
历史
- Python 2.2:PEP 255 在Python中引入生成器的概念以及yield语句(Statement)
- Python 2.4:PEP 289 引入生成器表达式
- Python 2.5:PEP 342 为生成器引入
send()
函数,yield语句(Statement)变为yield表达式(expression)。 - Python 3.3:PEP 380 引入 yield from。调用方、委派生成器(delegating generator)、子生成器(subgenerator)。
- Python 3.6:PEP 525 引入支持异步的生成器,以支持
async for
术语
在Python 3.4及之前,Python术语表中有两项:
- Generator:一个返回迭代器的函数。除了包含yield语句外,和普通函数一样。
- Generator expression:一个返回迭代器的表达式。
从Python 3.5起,术语表变为三项:
- Generator:一个返回迭代器的函数。【通常指代生成器函数(Generator function),在某些上下文系指代生成器迭代器(Generator iterator)】
- Generator iterator:生成器函数创建(返回)的对象。
- Generator expression:一个返回迭代器的表达式。
显然,官方注意到了Generator术语的混乱,并对此进行了一定程序的认可/澄清?
或许,可以先放一张图:
生成器(Generator iterator)就是迭代器
验证起来很简单:
1 2 |
|
或
1 2 |
|
有两种方式可以创建生成器迭代器:
- 生成器函数(Generator Function)
- 生成器表达式(Generator Expression)
接下来,我们通过具体例子,看一下:
生成器函数
一旦一个函数内部包含了yield,那么这个函数就是生成器(generator),也叫做生成器函数(generator function)。它的返回值是一个生成器迭代器(generator iterator)。
1 2 3 4 5 6 |
|
注意,此处没有使用typing中的Iterator 和 Generator【废弃】,而是使用推荐的collections.abc中的类型。
在代码中,生成器迭代器的类型是:Generator,而不是叫GeneratorIterator。再看看inspect中的这两个函数命名:
1 2 3 |
|
越发感觉Generator这个名字的精神分裂,应该就是官方的这个操作制造/助长了术语的混乱。
另外,异步形式也一样:
1 2 3 4 5 |
|
生成器表达式
Python中有一个推导式(comprehension)的概念,对list、set、dict 都适用:
1 2 3 4 5 6 7 8 9 10 11 |
|
强迫症感觉有点受不了,tuple的语法和其他三个不一样!
当适用圆括号时,返回是生成器迭代器,而不是tuple。而这个写法也就是生成器表达式:
1 2 3 4 5 |
|
为什么要有生成器?
PEP255对此说了好多,个人读不下去。但我想,或许一开始,只是因为手写一个迭代器(Iterator)的类太麻烦了,所以需要一个更简洁的函数方式来创建迭代器。
另外,Python手册](https://docs.python.org/3/library/stdtypes.html#generator-types) 中说:
Python’s generators provide a convenient way to implement the iterator protocol.
生成器(函数)为生成迭代器提供了一种便利的方式。
手写迭代器 vs 使用生成器(Generator Function)
依据迭代器协议(iterator protocol),创建一个迭代器,需要实现__iter__
和__next__
两个方法(或者__getitem__
方法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
确认一下:
1 2 3 |
|
注意,FirstN 只是按鸭子行为实现了iterator协议,并没有从Iterator派生,而isinstance()工作正常。
而使用生成器(Generator Function),代码可简化为:
1 2 3 4 5 6 7 8 9 10 11 |
|
前者需要先写一个类,实现协议要求的成员。后者只需要一个包含yield的函数,不用考虑底层的协议,就可以得到一个迭代器:
1 2 |
|
话说回来,生成器迭代器的类型是Generator,而普通迭代器类型是Iterator。说明二者还是有差异的,对比一下类型看看:
生成器(Generator Iterator) 与 迭代器(Iterator) 类型
types库
在标准库 types 中,定义了 GeneratorType 和 AsyncGeneratorType 类型,具体是这样的:
1 2 3 4 5 6 7 8 |
|
而对于Iterator,types源码中是这么说的:
1 2 3 4 |
|
typing库
在注解库 typing.py 中,定义了 Generator,AsyncGenerator,Iterator,AsyncIterator:
1 2 3 4 |
|
它们都是collections.abc 中对应类型的别名。已经废弃,新版本不建议适用。
collections.abc 库
源码位于 _collections_abc.py
,从这儿可以看到 Iterable、Iterator、Generator 的派生关系:
- Iterable:需要
__iter__
成员 - Iterator:需要
__iter__
和__next__
成员 - Generator:需要
__iter__
、__next__
、send
、throw
和close
成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
对于异步下的三兄弟AsyncIterable、AsyncIterator、AsyncGenerator,派生关系一样的:
- Iterable:需要
__aiter__
成员 - Iterator:需要
__aiter__
和__anext__
成员 - Generator:需要
__aiter__
、__anext__
、asend
、athrow
和aclose
成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
|
生成器(Generator Iterator)与迭代器(Iterator)
从前面可知:
- Iterator:需要
__iter__
和__next__
成员 - Generator Iterator:需要
__iter__
、__next__
、send
、throw
和close
成员
Generator Iterator 多出来的这三个成员,就是PEP 342 的内容。Python 2.5 时引入了 send、throw和close三个成员。
换句话说,在Python 2.5之前,没有Iterator和Generator Iterator的差异,生成器函数返回的就是普通的 Iterator。
回到文章开头的类图:实际上,__iter__()
和 __await__()
返回值都是迭代器(python库中甚至某些类定义中,为了兼容,还有__iter__ = __await__
的语句,比如asyncio.Future)。生成器和协程本质上,就是后者不需要用于实现迭代的__next__()
成员:
1 |
|
yield
yield 似乎非常简单,它的主要工作就是以类似return的方式控制生成器函数的流程。
在Python 2.5 之前,yield 是一个语句(Statement),而不是表达式(Expression)。
生成器(函数)只能产生输出;一旦生成器(函数)被调用并生成一个迭代器,恢复执行时将没有任何办法用于传输信息到生成器函数中。
这一时期的生成器函数属于简单的生成器函数:
1 2 3 4 5 |
|
作为语句,yield无法返回任何值,下面的写法是非法的
1 |
|
Python 2.5时,yield 由语句标为表达式,所以有了后面的写法:
1 2 3 4 5 6 7 8 |
|
从yield的角度看,它像一个”反“函数。
- 它的参数时要输出的内容
- 它的返回值,是要通过send() 传递进来的。否则,yield 表达式的值是 None。
In effect, a yield-expression is like an inverted function call; the argument to yield is in fact returned (yielded) from the currently executing function, and the return value of yield is the argument passed in via
send()
.
于是,生成器迭代器来说,除了可以像普通迭代器一样,使用next()进行遍历,直到遇到StopIteration异常:
1 2 3 4 5 6 7 8 9 10 11 |
|
还可以使用send:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
协程(coroutine)
在生成器函数中,当yield除了可以暂停执行并返回一个值外,还可以接受参数参数(通过send()
)并控制后续的执行分支的时候,这个生成器函数已经属于(经典)协程的概念了。
1 |
|
除了send()
外,throw()
和close()
也都是调用者干预生成器执行流程的手段。
经典协程,在同步场景下,多个协程函数交错执行,或许可以类比,早期单核电脑上的多线程程序。
廖雪峰的官方网站给了这样的生产者消费者例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
没有异步的加持,语法糖...
yield from
PEP 380引入术语:
-
委派生成器(delegating generator):包含yield from 表达式的生成器函数。
-
子生成器(sub generator):从yield from 表达式中获取的生成器。
-
调用方:调用委派生成器的客户端代码。
没有引入这个语法前,生成器要嵌套的话:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
有了yield from后,mytest可以进行改写
1 2 3 |
|
yield from 表达式所做的第一件事是,调用iter()
,从中获取迭代器。因此可以用于任何可迭代对象,比如:
1 2 3 4 5 6 |
|
注意,在关键词async和await引入前,asyncio曾使用yield from
实现基于生成器的协程
1 2 3 |
|
现在这种写法已经废弃。
参考
- https://docs.python.org/3/reference/expressions.html#yield-expressions
- https://docs.python.org/3/library/stdtypes.html#generator-types
- https://docs.python.org/3/glossary.html#term-generator
- https://docs.python.org/3.8/library/asyncio-task.html#generator-based-coroutines
- https://stackoverflow.com/questions/2776829/difference-between-pythons-generators-and-iterators
- https://en.wikipedia.org/wiki/Generator_(computer_programming)
- https://wiki.python.org/moin/Generators
- https://peps.python.org/pep-0255/
- https://peps.python.org/pep-0289/
- https://peps.python.org/pep-0342
- https://peps.python.org/pep-0380/
- https://peps.python.org/pep-0525/