前言
这篇文章大部分来自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。这个PPT很长而且非常烧脑,建议在阅读前应了解 Python 的生成器与携程相关知识,推荐《流畅的 Python》。
生成器(generator)
使用 yield
来定义一个生成器
def countdown(n):
while n > 0:
yield n
n -= 1
c = countdown(10)
c
生成器可用于迭代
for x in countdown(10):
print('倒数:', x)
倒数: 10
倒数: 9
倒数: 8
倒数: 7
倒数: 6
倒数: 5
倒数: 4
倒数: 3
倒数: 2
倒数: 1
可以通过 next()
来一步步地让生成器 yield
一个值,直到函数迭代器结束并抛出 StopIteration
。如果你对这一头雾水,建议阅读《Fluent Python》14.4 章。
这里 for
其实相当于不断地调用 next
并处理 StopIteration
c = countdown(3)
# next(c)
3
# next(c)
2
# next(c)
1
把生成器当作管道
你可以嵌套生成器,这会导致类似于 Unix 命令行管道的效果
def add_A(seq):
for item in seq:
yield item + '-A'
def add_B(seq):
for item in seq:
yield item + '-B'
def add_C(seq):
for item in seq:
yield item + '-C'
seq = ['apple', 'banana', 'orange']
stacked_generator = add_C(add_B(add_A(seq)))
for item in stacked_generator:
print(item)
apple-A-B-C
banana-A-B-C
orange-A-B-C
可以看到,我们为 seq
里的每项都依次添加了一个 tag。
yield 可以接受传值
yield
的作用是向调用者返回一个值,调用者其实也可以向生成器传值。
def receiver():
while True:
received_item = yield
print('收到:', received_item)
def caller():
recv = receiver()
next(recv) # 使生成器前进到 yield
for i in 'abcd':
recv.send(i)
caller()
收到: a
收到: b
收到: c
收到: d
那 send
函数的返回值是什么呢?
def receiver():
call_times = 0
while True:
item = yield call_times
print('收到:', item)
call_times += 1
def caller():
recv = receiver()
next(recv)
for i in 'abcd':
ret_value = recv.send(i)
print('返回值: ', ret_value)
caller()
收到: a
返回值: 1
收到: b
返回值: 2
收到: c
返回值: 3
收到: d
返回值: 4
所以 send
可以向生成器传值的同时会让生成器前进到下一个 yield
语句,并将 yield
右侧的值作为返回值。
生成器 101
-
yield
用于定义生成器函数 - 只要
yield
存在该函数必定是一个生成器 - 调用该函数返回一个生成器
让一个生成器前进
使用 next
使一个生成器前进到下一个 yield
语句处,并将产出值(yielded value)作为其返回值。使用 gen.__next__()
效果相同。
注意:这是一个新创建的生成器唯一允许的操作。
def generator():
yield 'a'
yield 'b'
gen = generator()
# next(gen)
'a'
# next(gen)
'b'
给生成器传值
可以通过调用生成器的 send
方法来向生成器传值,这将让生成器从上次暂停的 yield
前进到下个 yield
,并将产出值作为 send
的返回值。
def generator():
item = yield 'a'
print(item)
another_item = yield 'b'
gen = generator()
print(next(gen))
print(gen.send(1))
a
1
b
关闭一个生成器
通过调用生成器 close
方法可以生成器在 yield
语句处抛出 GeneratorExit
。这时仅允许 return
,如果没有捕捉这个错误,生成器会静默关闭,不抛出错误。
def generator():
times = 0
while True:
yield times
times += 1
gen = generator()
print(next(gen))
print(next(gen))
gen.close() # 不会抛出错误
0
1
抛出错误
调用生成器的 throw
方法可以在 yield
处抛出某个特定类型的错误,如果生成器内部可以捕捉这个错误,那生成器将前进到下个 yield
语句处,并将产出值作为 throw
的返回值,否则中止生成器。throw
的函数签名如下:
throw(typ, [,val, [,tb]])
其中 tyb
是某错误类型,val
是错误信息,tb
为 traceback。更多信息可以参考官方的PEP0342
def generator():
try:
yield 'apple'
except RuntimeError as e:
print('捕捉到:', e)
yield 'banana'
gen = generator()
print(next(gen))
print(gen.throw(RuntimeError, '运行错误'))
apple
捕捉到: 运行错误
banana
生成器返回值
如果在生成器函数中加上 return
那在运行到 return
时将会把返回值作为 StopIteration
的值传递出去。这个是 Python3 的特性,Python2 生成器不能返回某个值。
def generator():
yield
return 'apple'
g = generator()
next(g)
try:
next(g)
except StopIteration as e:
print(e)
apple
生成器委托
使用 yield from
可以帮你对一个生成器不断调用 next
函数,并返回生成器的返回值。言下之意是你可以在生成器里调用生成器。
def generator():
yield 'a'
yield 'b'
return 'c'
def func():
result = yield from generator()
print(result)
调用 func
结果是返回一个生成器
# func()
# next(func())
'a'
另外一个例子
def chain(x, y):
yield from x
yield from y
a = [1, 2, 3]
b = [4, 5, 6]
for i in chain(a, b):
print(i, end=' ')
1 2 3 4 5 6
c = [7, 8, 9]
for i in chain(a, chain(b, c)):
print(i, end=' ')
1 2 3 4 5 6 7 8 9
Part 1总结
生成器定义
def generator():
...
yield
...
return result
生成器操作
gen = generator()
# 使一个生成器前进
next(gen)
# 传递一个值
gen.send(item)
# 中止生成器
gen.close()
# 抛出错误
gen.throw(exc, val, tb)
# 委托
result = yield from gen