让我们先解决一件事.yield from g
等于for v in g: yield v
does not even begin to do justice的解释是yield from
的意思.因为,让我们面对现实吧,如果yield from
只是扩展了for
循环,那么就不需要向语言中添加yield from
,也不需要在Python 2中实现大量新特性.十、
yield from
意味着什么establishes a transparent bidirectional connection between the caller and the sub-generator:
(If we were talking about TCP, 100 might mean "now temporarily disconnect my client's socket and reconnect it to this other server socket".)
顺便说一句,如果你不确定sending data to a generator甚至意味着什么,你需要放下所有的东西,首先阅读coroutines,它们非常有用(与subroutines相比),但不幸的是在Python中不太为人所知.Dave Beazley's Curious Course on Coroutines是一个很好的开始.Read slides 24-33快速入门.
使用Year From从生成器读取数据
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
这是可行的,我们消除了一行代码.也许意图更清楚一点(或者不是).但生活不会改变.
使用yield 率将数据发送到生成器(协同程序)-第1部分
现在让我们做一些更有趣的事情.让我们创建一个名为writer
的协同程序,它接受发送给它的数据,并写入套接字、fd等.
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
现在的问题是,包装器函数应该如何处理向编写器发送数据,以便发送到包装器的任何数据都是transparently发送到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
.显然,仅仅做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行代码,使其更具可读性,而且可以正常工作.魔术!
将数据发送到生成器的成品率-第2部分-异常处理
让我们把它变得更复杂.如果我们的作者需要处理异常呢?假设writer
处理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 coro
yield from
透明地处理发送值或将值放入子生成器.
不过,这仍然不能涵盖所有的角落 case .如果外部发电机关闭会发生什么情况?如果子生成器返回值(是的,在Python3.3+中,生成器可以返回值),返回值应该如何传播?That yield from
transparently handles all the corner cases is really impressive.yield from
真的很神奇,可以处理所有这些案件.
我个人觉得yield from
是一个糟糕的关键词 Select ,因为它没有让two-way的本质变得明显.还提出了其他关键字(如delegate
个,但被拒绝了,因为向语言中添加一个新关键字比组合现有关键字困难得多.
总之,最好将yield from
想象成调用者和子生成器之间的101.
参考资料:
- PEP 380.授权给子生成器的语法(Ewing)[v3.3,2009-02-13]
- PEP 342 -