让我们先解决一件事。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
句柄a遇到一个SpamException
,它将打印***
。
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
视为 调用方和子生成方之间的。
参考文献: