对于大多数应用程序,yield from 只是按顺序生成左侧可迭代的所有内容:
def iterable1():
yield 1
yield 2
def iterable2():
yield from iterable1()
yield 3
assert list(iterable2) == [1, 2, 3]
对于看到这篇文章的 90% 的用户,我猜这对他们来说已经足够解释了。 yield from 只是委托到右侧的可迭代对象。
协程
但是,这里还有一些更深奥的生成器情况也很重要。关于生成器的一个鲜为人知的事实是它们可以用作协同程序。这并不常见,但您可以根据需要将数据发送到生成器:
def coroutine():
x = yield None
yield 'You sent: %s' % x
c = coroutine()
next(c)
print(c.send('Hello world'))
旁白:您可能想知道这个用例是什么(而且您并不孤单)。一个例子是contextlib.contextmanager 装饰器。协同程序也可用于并行化某些任务。我不知道有多少地方可以利用它,但是 google app-engine 的 ndb 数据存储 API 以一种非常漂亮的方式将它用于异步操作。
现在,假设您将send 数据发送到一个生成器,该生成器正在从另一个生成器产生数据……原始生成器如何得到通知?答案是它在 python2.x 中不需要你自己包装生成器:
def python2_generator_wapper():
for item in some_wrapped_generator():
yield item
至少不是没有很多痛苦:
def python2_coroutine_wrapper():
"""This doesn't work. Somebody smarter than me needs to fix it. . .
Pain. Misery. Death lurks here :-("""
# See https://www.python.org/dev/peps/pep-0380/#formal-semantics for actual working implementation :-)
g = some_wrapped_generator()
for item in g:
try:
val = yield item
except Exception as forward_exception: # What exceptions should I not catch again?
g.throw(forward_exception)
else:
if val is not None:
g.send(val) # Oops, we just consumed another cycle of g ... How do we handle that properly ...
使用yield from,这一切都变得微不足道:
def coroutine_wrapper():
yield from coroutine()
因为yield from 真正将(一切!)委托给底层生成器。
返回语义
请注意,所讨论的 PEP 也会更改返回语义。虽然不是直接在 OP 的问题中,但如果您愿意的话,值得快速题外话。在python2.x中,不能进行以下操作:
def iterable():
yield 'foo'
return 'done'
这是一个SyntaxError。随着yield的更新,上述功能不合法。同样,主要用例是协程(见上文)。您可以将数据发送到生成器,它可以神奇地完成它的工作(也许使用线程?),而程序的其余部分则执行其他操作。当流控制传回生成器时,StopIteration 将被提升(这对于生成器结束是正常的),但现在StopIteration 将有一个数据负载。就像程序员写的一样:
raise StopIteration('done')
现在调用者可以捕获该异常并使用数据负载做一些事情来造福其他人。