Python yield使用详解(一)
2017-12-16 本文已影响1706人
寻找无双丶
生成器
yield语句可以作为生成器
def countdown(n):
while n > 0:
yield n
n -= 1
# 可以当迭代器来使用它
for x in countdown(10):
print('T-minus', x)
# 可以使用next()来产出值,当生成器函数return(结束)时,报错。
>>> c = countdown(3)
>>> c
<generator object countdown at 0x10064f900>
>>> next(c)
3
>>> next(c)
2
>>> next(c)
1
>>> next(c)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration
>>>
这篇文章我着重讲yield作为协程
的使用方法,作为生成器的话我一笔带过,想要仔细了解迭代器
与生成器
使用,我这里推荐个教程。完全理解Python迭代对象、迭代器、生成器 ,很棒,还有的话就是与生成器密切相关的itertools模块,可以了解下。但是我在讲yield协程
之前我再给出一张图来说yield一个有趣的用法。
生成器类似于UNIX管道的作用
这个process会有难以置信的作用,比如实现UNIX中grep的作用。不展开,以后肯定会用到它。
生成器进化为协程
一个协程例子
重头戏来了。
如果你想更多的使用yield,那么就是协程了。协程就不仅仅是产出值了,而是能消费发送给它的值。
那么这里的例子就用协程实现上面的UNIX的grep作用
def grep(pattern):
print("Looking for {}".format(pattern))
while True:
line = yield
if pattern in line:
print('{} : grep success '.format(line))
>>> g=grep('python')
# 还是个生成器
>>> g
<generator object grep at 0x7f17e86f3780>
# 激活协程!只能用一次,也可以用g.send(None)来代替next(g)
>>> next(g)
Looking for python
# 使用.send(...)发送数据,发送的数据会成为生成器函数中yield表达式值,即变量line的值
>>> g.send("Yeah, but no, but yeah, but no")
>>> g.send("A series of tubes")
# 协程,协程,就是互相协作的程序,我发数据过去然后你协助我一下看看grep成功没
>>> g.send("python generators rock!")
python generators rock! : grep success
# 关闭
>>> g.close()
例子讲完了。有几个注意点:
- 生成器用于生成供迭代的数据
- 协程是数据的消费者
- 为了避免脑袋炸裂,不能把两个概念混为一谈
- 协程与迭代无关
- 注意,虽然在协程值会使用yield产出值,但这与迭代无关
发送数据给协程
预激活,到yield处暂停。然后发送item值,协程继续了,协程中item接收到发送的那个值,然后到下一个yield再暂停。
使用一个装饰器
如果不预激(primer
),那么协程没什么用,调用g.send(x)之前。记住一定要调用next(g)。为了简化协程用法,有时会使用一个预激装饰器,如下。
def coroutine(func):
def primer(*args,**kwargs):
cr = func(*args,**kwargs)
next(cr)
return cr
return primer
@coroutine
def grep(pattern):
...
关闭一个协程
- 一个协程有可能永远运行下去
- 可以 .close()让它停下来
例子中已经体现,不展开。
捕捉close()
def grep(pattern):
print("Looking for {}".format(pattern))
try:
while True:
line = yield
if pattern in line:
print(line)
except GeneratorExit:
print("Going away. Goodbye")
捕捉到.close()方法,然后会打印"Going away. Goodbye"
。
抛出异常
>>> g = grep("python")
>>> next(g) # Prime it
Looking for python
>>> g.send("python generators rock!")
python generators rock! : grep success
>>> g.throw(RuntimeError,"You're hosed")
Traceback (most recent call last):
.....
.....
RuntimeError: You're hosed
>>>
说明:
- 在协程内部能抛出一个异常
- 异常发生于yield表达式
- 不慌,我们可以平常的方法处理它
生成器返回数值
鉴于上面的例子是一直run下去的,所以稍加修改:
def grep(pattern):
print("Looking for {}".format(pattern))
while True:
line = yield
# 当发送的数据为None时,跳出while循环
if line is None:
break
else:
if pattern in line:
print('{} : grep success '.format(line))
return 'End'
>>> ..... 省略
>>> g.send(None)
Traceback (most recent call last):
...
...
StopIteration: End
# 这里可以用try捕捉异常,异常对象的value属性保存着返回的值
try:
g.send(None)
except StopIteration as exc:
result = exc.value
>>> result
End
图解如下
说明:
- 通过捕捉异常获取返回值
- 只支持python3
总结
-
yield
的基本用法已经差不多了,有两个方面:生成器与协程(理解协程的关键在于明白它在何处暂停发送出的数据传到了哪个变量) -
yield
的另一方面的应用是上下文管理器下一节讲 -
yield from
我这里暂时不讲,留到后面。yield from
会在内部自动捕获StopIteration
异常等
参考资料
David beazley协程
Fluent Python