- 问题:
- 答案:
-
让我们先解决一件事。关于g的
收益等于g中v的
的解释:yield v
甚至没有开始对从
中得到的东西做出公正的。因为,让我们面对它,如果yield from
所做的只是扩展for
循环,那么它就不能保证将yield from
添加到语言中,并阻止在Python2.x中实现一大堆新特性的结果是在调用者和子生成器之间建立一个透明的双向连接
连接是“透明的”,因为它将正确地传播一切,而不仅仅是生成的元素(例如,传播异常)
这种连接是“双向”的,即数据既可以从生成器发送到生成器
(如果我们讨论的是TCP,
yield from g
可能意味着“现在暂时断开客户机的套接字,并将其重新连接到另一个服务器套接字”。)顺便说一句,如果您不确定向生成器发送数据意味着什么,那么您需要删除所有内容,并首先阅读有关协程的信息,它们非常有用(与子例程对比),但不幸的是在Python中知之甚少。Dave Beazley's Curious Course on Coroutinesis an excellent start.
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()
,而只需从中生成
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)现在的问题是,wrapper函数应该如何处理向writer发送数据,以便发送到wrapper的任何数据都透明地发送到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包装器需要接受发送给它的数据(显然),并且在for循环用尽时还应该处理
StopIteration
。显然,仅仅在coro中对x执行操作: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行代码,使其可读性大大提高,而且可以正常工作。神奇!在
让我们把它变得更复杂。如果我们的作者需要处理异常呢?假设
编写器
处理SpamException
并在遇到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 coroyield from
透明地处理向子生成器发送值或将值抛出的问题但这仍然不能涵盖所有的角落案件。如果外部发电机关闭会发生什么?如果子生成器返回一个值(是的,在Python3.3+中,生成器可以返回值),那么返回值应该如何传播?That
yield from
transparently handles all the corner cases is really impressive.yield from
只是神奇地工作并处理所有这些情况我个人觉得
yield from
是一个糟糕的关键字选择,因为它不能使双向的自然变得明显。还有其他关键字被提议(比如delegate
,但是被拒绝了,因为在语言中添加一个新的关键字比合并现有的关键字要困难得多总之,最好将
的yield from
看作是调用者和子生成器之间的一个透明的双向通道参考文献: