我很难把我的大脑围绕在PEP 380上。
相比?
更新
现在我明白我遇到困难的原因了。我使用过生成器,但从未真正使用过协程(由PEP-342引入)。尽管有一些相似之处,生成器和协程基本上是两个不同的概念。理解协程(不仅仅是生成器)是理解新语法的关键。
协程是Python语言中最晦涩难懂的特性,大多数书籍都让它显得无用和无趣。
感谢你的精彩回答,但特别感谢agf和他链接到David Beazley presentations的评论。大卫很棒。
发布于 2014-09-30 05:22:58
让我们先做一件事。yield from g等同于for v in g: yield v 的解释甚至不能公正地解释关于yield from的所有内容。因为,让我们面对它,如果yield from所做的只是扩展for循环,那么它并不保证将yield from添加到语言中,并且排除了一大堆新特性在Python2.x中的实现。
yield from所做的是它在调用者和子生成器之间建立一个透明的双向连接
(如果我们谈论的是TCP,yield from g可能意味着“现在暂时断开客户端的套接字,并将其重新连接到另一个服务器套接字”。)
顺便说一句,如果你不确定发送数据到生成器是什么意思,你需要先放下所有的东西,阅读关于协程的知识-它们非常有用(与子例程对比),但不幸的是,在Python中鲜为人知。Dave Beazley's Curious Course on Coroutines是一个很好的开始。快速入门的Read slides 24-33。
使用yield从生成器读取数据
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我们可以直接yield from它,而不是手动迭代它。
def reader_wrapper(g):
yield from g这样就行了,我们去掉了一行代码。而且其意图可能会更清晰一些(或者不清楚)。但生活没有任何改变。
使用yield from将数据发送到生成器(协程)-第1部分
现在让我们做一些更有趣的事情。让我们创建一个名为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包装器需要接受发送给它的数据(显然),并且还应该在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,如果遇到***,它会打印出来。
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透明地处理发送值或将值抛入子生成器。
然而,这仍然不能涵盖所有的角落案例。如果外部发电机关闭,会发生什么情况?如果子生成器返回一个值(是的,在Python 3.3+中,生成器可以返回值),应该如何传播返回值呢?That yield from transparently handles all the corner cases is really impressive。yield from可以神奇地工作并处理所有这些情况。
我个人认为yield from是一个糟糕的关键字选择,因为它没有明显的双向性质。还提出了其他关键字(如delegate ),但被拒绝了,因为向语言中添加新关键字比组合现有关键字要困难得多。
总之,最好将yield from看作调用者和子生成器之间的transparent two way channel。
参考文献:
发布于 2016-12-27 23:47:37
一个简短的例子将帮助您理解yield from的一个用例:从另一个生成器获取值
def flatten(sequence):
"""flatten a multi level list or something
>>> list(flatten([1, [2], 3]))
[1, 2, 3]
>>> list(flatten([1, [2], [3, [4]]]))
[1, 2, 3, 4]
"""
for element in sequence:
if hasattr(element, '__iter__'):
yield from flatten(element)
else:
yield element
print(list(flatten([1, [2], [3, [4]]])))发布于 2018-08-27 05:05:08
在Asynchronous IO coroutine的实际应用中,yield from的行为类似于coroutine function中的await。这两个函数都用于暂停协程的执行。
对于Asyncio,如果不需要支持较旧的Python版本(例如,>3.5),async def/await是定义协程的推荐语法。因此,在协程中不再需要yield from。
但一般而言,在asyncio之外,yield from <sub-generator>在迭代sub-generator时仍有一些其他用法,如前面的答案所述。
https://stackoverflow.com/questions/9708902
复制相似问题