我很难理解PEP 380。
在什么情况下yield from有用?
经典的用例是什么?
为什么要与微线程进行比较?
到目前为止,我使用过生成器,但从未真正使用过协程(由PEP-342引入)。尽管有一些相似之处,但生成器和协程基本上是两个不同的概念。理解协程(而不仅仅是生成器)是理解新语法的关键。
我认为协程是 Python 中最晦涩的功能,大多数书籍都使它看起来无用且无趣。
首先让我们解决一个问题。yield from g
相当于的解释for v in g: yield v
甚至没有开始公正地解释这yield from
一切。因为,让我们面对现实吧,如果yield from
所做的只是扩展for
循环,那么它就没有理由添加yield from
到语言中,并且会阻止在 Python 2.x 中实现一大堆新功能。
它的作用yield from
是在调用者和子生成器之间建立透明的双向连接:
(如果我们谈论的是 TCP,yield from g
可能意味着“现在暂时断开我的客户端套接字并将其重新连接到另一个服务器套接字”。)
顺便说一句,如果你不确定向生成器发送数据意味着什么,你需要先放下一切,阅读关于协程的文章——它们非常有用(与子程序形成对比),但不幸的是在 Python 中鲜为人知。Dave Beazley 的《协程奇妙课程》是一个很好的开始。阅读幻灯片 24-33以快速入门。
def reader():
"""A generator that fakes a read from a file, socket, etc."""
for i in range(4):
yield '<< %s' % i
def reader_wrapper(g):
# Manually iterate over data produced by reader
for v in g:
yield v
wrap = reader_wrapper(reader())
for i in wrap:
print(i)
# Result
<< 0
<< 1
<< 2
<< 3
我们无需手动进行迭代reader()
,只需进行yield from
迭代即可。
def reader_wrapper(g):
yield from g
这很有效,我们省去了一行代码。而且意图可能更清晰一些(或不清晰)。但这并没有什么改变。
现在让我们做一些更有趣的事情。让我们创建一个名为的协程writer
,它接受发送给它的数据并写入套接字、fd 等。
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
现在的问题是,包装器函数应如何处理将数据发送到编写器,以便将发送到包装器的任何数据透明地发送到writer()
?
def writer_wrapper(coro):
# TBD
pass
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in range(4):
wrap.send(i)
# Expected result
>> 0
>> 1
>> 2
>> 3
包装器需要接受发送给它的数据(显然),还应该处理StopIteration
for 循环耗尽的情况。显然,只这样做for x in coro: yield x
是不行的。这是一个可行的版本。
def writer_wrapper(coro):
coro.send(None) # prime the coro
while True:
try:
x = (yield) # Capture the value that's sent
coro.send(x) # and pass it to the writer
except StopIteration:
pass
或者,我们可以这样做。
def writer_wrapper(coro):
yield from coro
这节省了 6 行代码,使其更具可读性,并且它确实有效。太神奇了!
让我们让它更复杂一些。如果我们的编写器需要处理异常怎么办?假设处理writer
aSpamException
并且***
如果遇到异常,它会打印出来。
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
如果我们不改变会怎么样writer_wrapper
?这有效吗?我们试试吧
# writer_wrapper same as above
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in [0, 1, 2, 'spam', 4]:
if i == 'spam':
wrap.throw(SpamException)
else:
wrap.send(i)
# Expected Result
>> 0
>> 1
>> 2
***
>> 4
# Actual Result
>> 0
>> 1
>> 2
Traceback (most recent call last):
... redacted ...
File ... in writer_wrapper
x = (yield)
__main__.SpamException
嗯,它不起作用,因为x = (yield)
只是引发异常,一切都会突然停止。让我们让它工作,但手动处理异常并将它们发送或抛入子生成器(writer
)
def writer_wrapper(coro):
"""Works. Manually catches exceptions and throws them"""
coro.send(None) # prime the coro
while True:
try:
try:
x = (yield)
except Exception as e: # This catches the SpamException
coro.throw(e)
else:
coro.send(x)
except StopIteration:
pass
这有效。
# Result
>> 0
>> 1
>> 2
***
>> 4
但这也一样!
def writer_wrapper(coro):
yield from coro
透明地处理yield from
发送值或将值投入子生成器。
但这仍然没有涵盖所有极端情况。如果外部生成器关闭会发生什么?如果子生成器返回一个值(是的,在 Python 3.3+ 中,生成器可以返回值),返回值应该如何传播?透明地yield from
处理所有极端情况确实令人印象深刻。yield from
神奇地工作并处理所有这些情况。
我个人认为yield from
这是一个糟糕的关键字选择,因为它没有使双向性显而易见。还有其他关键字被提议(例如,delegate
但被拒绝,因为在语言中添加新关键字比组合现有关键字要困难得多。
总而言之,最好将其视为yield from
调用transparent two way channel
者与子生成器之间的。
参考: