注意:生成器(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)就是迭代器
验证起来很简单:
| from collections.abc import Iterator, Generator
assert issubclass(Generator, Iterator)
|
或
| from collections.abc import AsyncIterator, AsyncGenerator
assert issubclass(AsyncGenerator, AsyncIterator)
|
有两种方式可以创建生成器迭代器:
- 生成器函数(Generator Function)
- 生成器表达式(Generator Expression)
接下来,我们通过具体例子,看一下:
生成器函数
一旦一个函数内部包含了yield,那么这个函数就是生成器(generator),也叫做生成器函数(generator function)。它的返回值是一个生成器迭代器(generator iterator)。
| def gen():
yield 123
from collections.abc import Iterator, Generator
assert isinstance(gen(), Iterator)
assert isinstance(gen(), Generator)
|
注意,此处没有使用typing中的Iterator 和 Generator【废弃】,而是使用推荐的collections.abc中的类型。
在代码中,生成器迭代器的类型是:Generator,而不是叫GeneratorIterator。再看看inspect中的这两个函数命名:
| import inspect
assert inspect.isgeneratorfunction(gen)
assert inspect.isgenerator(gen())
|
越发感觉Generator这个名字的精神分裂,应该就是官方的这个操作制造/助长了术语的混乱。
另外,异步形式也一样:
| async def agen():
yield 123
assert isinstance(agen(), AsyncIterator)
assert isinstance(agen(), AsyncGenerator)
|
生成器表达式
Python中有一个推导式(comprehension)的概念,对list、set、dict 都适用:
| my_list = [i for i in range(10)]
assert isinstance(my_list, list)
my_set = {i for i in range(10)}
assert isinstance(my_set, set)
my_dict = {i: i for i in range(10)}
assert isinstance(my_dict, dict)
my_tuple = tuple(i for i in range(10))
assert isinstance(my_tuple, tuple)
|
强迫症感觉有点受不了,tuple的语法和其他三个不一样!
当适用圆括号时,返回是生成器迭代器,而不是tuple。而这个写法也就是生成器表达式:
| my_gen = (i*i for i in range(10))
from collections.abc import Iterator, Generator
assert isinstance(my_gen, Iterator)
assert isinstance(my_gen, Generator)
|
为什么要有生成器?
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 | class FirstN:
def __init__(self, n):
self.n = n
self.num = 0
def __iter__(self):
return self
def __next__(self):
if self.num < self.n:
cur, self.num = self.num, self.num + 1
return cur
raise StopIteration()
for i in firstN(10):
print(i)
|
确认一下:
| assert isinstance(FirstN(10), Iterator)
print(FirstN.__mro__)
|
注意,FirstN 只是按鸭子行为实现了iterator协议,并没有从Iterator派生,而isinstance()工作正常。
而使用生成器(Generator Function),代码可简化为:
| def firstN(n):
num = 0
while num < n:
yield num
num += 1
for i in firstN(10):
print(i)
assert hasattr(firstN(10), '__iter__')
assert hasattr(firstN(10), '__next__')
|
前者需要先写一个类,实现协议要求的成员。后者只需要一个包含yield的函数,不用考虑底层的协议,就可以得到一个迭代器:
| assert isinstance(firstN(10), Iterator)
assert isinstance(firstN(10), Generator)
|
话说回来,生成器迭代器的类型是Generator,而普通迭代器类型是Iterator。说明二者还是有差异的,对比一下类型看看:
生成器(Generator Iterator) 与 迭代器(Iterator) 类型
types库
在标准库 types 中,定义了 GeneratorType 和 AsyncGeneratorType 类型,具体是这样的:
| def _g():
yield 1
GeneratorType = type(_g())
async def _ag():
yield
_ag = _ag()
AsyncGeneratorType = type(_ag)
|
而对于Iterator,types源码中是这么说的:
| # Iterators in Python aren't a matter of type but of protocol. A large
# and changing number of builtin types implement *some* flavor of
# iterator. Don't check the type! Use hasattr to check for both
# "__iter__" and "__next__" attributes instead.
|
typing库
在注解库 typing.py 中,定义了 Generator,AsyncGenerator,Iterator,AsyncIterator:
| Generator = _alias(collections.abc.Generator, 3)
AsyncGenerator = _alias(collections.abc.AsyncGenerator, 2)
AsyncIterator = _alias(collections.abc.AsyncIterator, 1)
Iterator = _alias(collections.abc.Iterator, 1)
|
它们都是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 | class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
return _check_methods(C, '__iter__', '__next__')
return NotImplemented
class Generator(Iterator):
__slots__ = ()
def __next__(self):
"""Return the next item from the generator.
When exhausted, raise StopIteration.
"""
return self.send(None)
@abstractmethod
def send(self, value):
"""Send a value into the generator.
Return next yielded value or raise StopIteration.
"""
raise StopIteration
@abstractmethod
def throw(self, typ, val=None, tb=None):
"""Raise an exception in the generator.
Return next yielded value or raise StopIteration.
"""
if val is None:
if tb is None:
raise typ
val = typ()
if tb is not None:
val = val.with_traceback(tb)
raise val
def close(self):
"""Raise GeneratorExit inside generator.
"""
try:
self.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
pass
else:
raise RuntimeError("generator ignored GeneratorExit")
@classmethod
def __subclasshook__(cls, C):
if cls is Generator:
return _check_methods(C, '__iter__', '__next__',
'send', 'throw', 'close')
return NotImplemented
|
对于异步下的三兄弟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 | class AsyncIterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __aiter__(self):
return AsyncIterator()
@classmethod
def __subclasshook__(cls, C):
if cls is AsyncIterable:
return _check_methods(C, "__aiter__")
return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
class AsyncIterator(AsyncIterable):
__slots__ = ()
@abstractmethod
async def __anext__(self):
"""Return the next item or raise StopAsyncIteration when exhausted."""
raise StopAsyncIteration
def __aiter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is AsyncIterator:
return _check_methods(C, "__anext__", "__aiter__")
return NotImplemented
class AsyncGenerator(AsyncIterator):
__slots__ = ()
async def __anext__(self):
"""Return the next item from the asynchronous generator.
When exhausted, raise StopAsyncIteration.
"""
return await self.asend(None)
@abstractmethod
async def asend(self, value):
"""Send a value into the asynchronous generator.
Return next yielded value or raise StopAsyncIteration.
"""
raise StopAsyncIteration
@abstractmethod
async def athrow(self, typ, val=None, tb=None):
"""Raise an exception in the asynchronous generator.
Return next yielded value or raise StopAsyncIteration.
"""
if val is None:
if tb is None:
raise typ
val = typ()
if tb is not None:
val = val.with_traceback(tb)
raise val
async def aclose(self):
"""Raise GeneratorExit inside coroutine.
"""
try:
await self.athrow(GeneratorExit)
except (GeneratorExit, StopAsyncIteration):
pass
else:
raise RuntimeError("asynchronous generator ignored GeneratorExit")
@classmethod
def __subclasshook__(cls, C):
if cls is AsyncGenerator:
return _check_methods(C, '__aiter__', '__anext__',
'asend', 'athrow', 'aclose')
return NotImplemented
|
生成器(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__()
成员:
| 生成器Generator = 迭代器Iterator + 协程Coroutine
|
yield
yield 似乎非常简单,它的主要工作就是以类似return的方式控制生成器函数的流程。
在Python 2.5 之前,yield 是一个语句(Statement),而不是表达式(Expression)。
生成器(函数)只能产生输出;一旦生成器(函数)被调用并生成一个迭代器,恢复执行时将没有任何办法用于传输信息到生成器函数中。
这一时期的生成器函数属于简单的生成器函数:
| def firstN(maximum):
i = 0
while i < maximum:
yield i
i += 1
|
作为语句,yield无法返回任何值,下面的写法是非法的
Python 2.5时,yield 由语句标为表达式,所以有了后面的写法:
| def firstN(maximum):
i = 0
while i < maximum:
val = (yield i)
if val is not None:
i = val
else:
i += 1
|
从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异常:
| def firstN(n):
num = 0
while num < n:
yield num
num += 1
gen = firstN(3)
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
|
还可以使用send:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | def firstN(maximum):
i = 0
while i < maximum:
val = (yield i)
if val is not None:
i = val
else:
i += 1
gen = firstN(3)
print(next(gen))
print(gen.send(0))
print(gen.send(0))
print(next(gen))
|
协程(coroutine)
在生成器函数中,当yield除了可以暂停执行并返回一个值外,还可以接受参数参数(通过send()
)并控制后续的执行分支的时候,这个生成器函数已经属于(经典)协程的概念了。
| 生成器Generator = 迭代器Iterator + 协程Coroutine
|
除了send()
外,throw()
和close()
也都是调用者干预生成器执行流程的手段。
经典协程,在同步场景下,多个协程函数交错执行,或许可以类比,早期单核电脑上的多线程程序。
廖雪峰的官方网站给了这样的生产者消费者例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
c.close()
c = consumer()
produce(c)
|
没有异步的加持,语法糖...
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 | def hello():
for i in range(5):
yield i
def debao():
for i in range(10, 15):
yield i
def mytest():
for v in hello():
yield v
for v in debao():
yield v
for v in mytest():
print(v)
|
有了yield from后,mytest可以进行改写
| def mytest():
yield from hello()
yield from debao()
|
yield from 表达式所做的第一件事是,调用iter()
,从中获取迭代器。因此可以用于任何可迭代对象,比如:
| def gen():
yield from 'debao'
yield from range(5)
a = list(gen())
print(a)
|
注意,在关键词async和await引入前,asyncio曾使用yield from
实现基于生成器的协程
| @asyncio.coroutine
def old_style_coroutine():
yield from asyncio.sleep(1)
|
现在这种写法已经废弃。
参考
- 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/