这篇文章大部分来自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。这个PPT很长并且很是烧脑,建议在阅读前应了解 Python 的生成器与携程相关知识,推荐《流畅的 Python》。python
使用 yield
来定义一个生成器app
def countdown(n): while n > 0: yield n n -= 1 c = countdown(10) c
<generator object countdown at 0x0000021F5EAB9F10>
生成器可用于迭代函数
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 命令行管道的效果code
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。get
yield
的做用是向调用者返回一个值,调用者其实也能够向生成器传值。generator
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
右侧的值做为返回值。it
yield
用于定义生成器函数yield
存在该函数一定是一个生成器使用 next
使一个生成器前进到下一个 yield
语句处,并将产出值(yielded value)做为其返回值。使用 gen.__next__()
效果相同。io
注意:这是一个新建立的生成器惟一容许的操做。
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() <generator object func at 0x0000021F5EB0F990> # 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
def generator(): ... yield ... return result
gen = generator() # 使一个生成器前进 next(gen) # 传递一个值 gen.send(item) # 停止生成器 gen.close() # 抛出错误 gen.throw(exc, val, tb) # 委托 result = yield from gen