我很难将我的大脑包裹在PEP 380周围.
[更新]
现在我明白了我的困难的原因.我使用过发电机,但从未真正使用过协程(由PEP-342引入).尽管有一些相似之处,但生成器和协同程序基本上是两个不同的概念.理解协同程序(不仅仅是生成器)是理解新语法的关键.
恕我直言协程是最晦涩的Python功能,大多数书籍使它看起来毫无用处和无趣.
感谢您的回答,但特别感谢agf和他与David Beazley演讲相关的评论.大卫摇滚.
Pra*_*ota 490
让我们先解决一件事.该解释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在Couroutines上的好奇课程是一个很好的开始.阅读幻灯片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
Run Code Online (Sandbox Code Playgroud)
reader()我们可以yield from做到,而不是手动迭代.
def reader_wrapper(g):
yield from g
Run Code Online (Sandbox Code Playgroud)
这很有效,我们删除了一行代码.也许意图有点清晰(或不是).但没有改变生活.
现在让我们做一些更有趣的事情.让我们创建一个调用的协程writer,它接受发送给它的数据并写入socket,fd等.
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
Run Code Online (Sandbox Code Playgroud)
现在的问题是,包装器函数应该如何处理向writer发送数据,以便发送给包装器的任何数据都透明地发送给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
Run Code Online (Sandbox Code Playgroud)
包装器需要接受发送给它的数据(显然),并且还应该处理StopIterationfor循环耗尽的时间.显然只是做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
Run Code Online (Sandbox Code Playgroud)
或者,我们可以做到这一点.
def writer_wrapper(coro):
yield from coro
Run Code Online (Sandbox Code Playgroud)
这样可以节省6行代码,使其更具可读性,而且只是有效.魔法!
让它变得更复杂.如果我们的作家需要处理异常怎么办?让我们说writer句柄a SpamException,***如果它遇到一个它就打印出来.
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
Run Code Online (Sandbox Code Playgroud)
如果我们不改变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
Run Code Online (Sandbox Code Playgroud)
嗯,它不起作用,因为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
Run Code Online (Sandbox Code Playgroud)
这有效.
# Result
>> 0
>> 1
>> 2
***
>> 4
Run Code Online (Sandbox Code Playgroud)
但这也是如此!
def writer_wrapper(coro):
yield from coro
Run Code Online (Sandbox Code Playgroud)
该yield from透明地处理发送值或抛出的值到副发电机.
尽管如此,这仍然不能涵盖所有角落的情况.如果外部发电机关闭会发生什么?如果子生成器返回一个值(是的,在Python 3.3+中,生成器可以返回值),那么返回值应该如何传播呢?这yield from透明地处理所有角落的情况下确实是令人印象深刻.yield from只是神奇地工作并处理所有这些案件.
我个人认为这yield from是一个糟糕的关键词选择,因为它不会使双向性质明显.提出了其他关键字(delegate但是被拒绝了,因为在语言中添加新关键字比组合现有关键字要困难得多.
总之,最好将其yield from视为transparent two way channel调用者和子生成器之间的关系.
参考文献:
Nik*_* B. 80
"收益率"有用的情况是什么?
你有这样一个循环的每种情况:
for x in subgenerator:
yield x
Run Code Online (Sandbox Code Playgroud)
正如PEP所描述的那样,这是一个相当天真的尝试使用子发电机,它缺少几个方面,尤其是PEP 342引入的.throw()/ .send()/ .close()机制的正确处理.要做到这一点,需要相当复杂的代码.
什么是经典用例?
考虑您要从递归数据结构中提取信息.假设我们想要获取树中的所有叶节点:
def traverse_tree(node):
if not node.children:
yield node
for child in node.children:
yield from traverse_tree(child)
Run Code Online (Sandbox Code Playgroud)
更重要的是,在此之前yield from,没有简单的重构生成器代码的方法.假设您有一个(无意义的)生成器,如下所示:
def get_list_values(lst):
for item in lst:
yield int(item)
for item in lst:
yield str(item)
for item in lst:
yield float(item)
Run Code Online (Sandbox Code Playgroud)
现在,您决定将这些循环分解为单独的生成器.没有yield from,这是丑陋的,直到你会想三次是否真的想要这样做.有了yield from,看看真的很好:
def get_list_values(lst):
for sub in [get_list_values_as_int,
get_list_values_as_str,
get_list_values_as_float]:
yield from sub(lst)
Run Code Online (Sandbox Code Playgroud)
为什么它与微线程相比?
我认为PEP中的这一部分所讨论的是每个生成器都有自己独立的执行上下文.与使用yield和__next__()分别在生成器 - 迭代器和调用者之间切换执行的事实一起,这类似于线程,其中操作系统不时地切换执行线程,以及执行上下文(堆栈,寄存器, ...).
这种效果也是可比的:生成器迭代器和调用者同时在执行状态中进行,它们的执行是交错的.例如,如果生成器进行某种计算并且调用者打印出结果,您将在结果可用时立即看到结果.这是一种并发形式.
尽管如此,这个类比并不是特定的yield from- 它是Python中生成器的一般属性.
Ben*_*son 29
无论你从发电机内调用发电机,都需要一个"泵"来重新定义yield值: for v in inner_generator: yield v.正如人民党指出的那样,大多数人都忽略了这种复杂性.非本地流量控制就像throw()是PEP中给出的一个例子.yield from inner_generator无论您何时编写显式for循环,都会使用新语法.不过,它不仅仅是语法糖:它处理for循环忽略的所有极端情况."含糖"鼓励人们使用它,从而获得正确的行为.
讨论主题中的这条消息谈到了这些复杂性:
由于PEP 342引入了额外的生成器功能,情况不再如此:如Greg的PEP中所述,简单的迭代不能正确支持send()和throw().当你打破它们时,支持send()和throw()所需的体操实际上并不复杂,但它们也不是微不足道的.
我不能说与微线程进行比较,除了观察发电机是一种类型的并行性.您可以将挂起的生成器视为将值发送yield到使用者线程的线程.实际的实现可能与此类似(并且实际的实现显然是Python开发人员非常感兴趣的),但这与用户无关.
新yield from语法在线程方面不会为语言添加任何其他功能,只是使正确使用现有功能变得更加容易.或者更确切地说,它使得专家编写的复杂内部生成器的新手消费者更容易通过该生成器而不会破坏其任何复杂特征.
osp*_*der 21
一个简短的例子将帮助您理解其中一个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]]])))
Run Code Online (Sandbox Code Playgroud)
kat*_*330 13
yield将产生单一值到集合中。
yield from将把集合变成集合并使其扁平化。
检查这个例子:
def yieldOnly():
yield "A"
yield "B"
yield "C"
def yieldFrom():
for i in [1, 2, 3]:
yield from yieldOnly()
test = yieldFrom()
for i in test:
print(i)
Run Code Online (Sandbox Code Playgroud)
在控制台中您将看到:
A
B
C
A
B
C
A
B
C
Run Code Online (Sandbox Code Playgroud)
yield from 基本上以有效的方式链接迭代器:
# chain from itertools:
def chain(*iters):
for it in iters:
for item in it:
yield item
# with the new keyword
def chain(*iters):
for it in iters:
yield from it
Run Code Online (Sandbox Code Playgroud)
如您所见,它删除了一个纯 Python 循环。这几乎就是它所做的一切,但是链接迭代器是 Python 中非常常见的模式。
线程基本上是一个特性,它允许你在完全随机的点跳出函数并跳回到另一个函数的状态。线程主管经常这样做,因此程序似乎同时运行所有这些功能。问题是点是随机的,所以你需要使用锁定来防止主管在有问题的点停止功能。
从这个意义上说,生成器与线程非常相似:它们允许您指定yield可以跳入和跳出的特定点(无论何时)。以这种方式使用时,生成器称为协程。
阅读这篇关于 Python 中协程的优秀教程以获取更多详细信息
| 归档时间: |
|
| 查看次数: |
93109 次 |
| 最近记录: |