我很难理解PEP 380。
在什么情况下yield from是有用的? 经典用例是什么? 为什么将其与微线程进行比较?
到目前为止,我使用过生成器,但从未真正使用过协程(由PEP-342引入)。尽管有一些相似之处,生成器和协程基本上是两个不同的概念。理解协程(不仅仅是生成器)是理解新语法的关键。
以我之见,协程是Python中最晦涩的特性,大多数书籍都让它看起来毫无用处和无趣。
感谢这些精彩的回答,但特别感谢agf和他的评论链接到David Beazley的演讲。
我很难理解PEP 380。
在什么情况下yield from是有用的? 经典用例是什么? 为什么将其与微线程进行比较?
到目前为止,我使用过生成器,但从未真正使用过协程(由PEP-342引入)。尽管有一些相似之处,生成器和协程基本上是两个不同的概念。理解协程(不仅仅是生成器)是理解新语法的关键。
以我之见,协程是Python中最晦涩的特性,大多数书籍都让它看起来毫无用处和无趣。
感谢这些精彩的回答,但特别感谢agf和他的评论链接到David Beazley的演讲。
当前回答
在异步IO协程的实际使用中,yield from与协程函数中的await具有类似的行为。两者都用于暂停协程的执行。
Yield from由基于生成器的协程使用。 Await用于async def协程。(Python 3.5+开始)
对于Asyncio,如果不需要支持旧的Python版本(即>3.5),async def/await是定义协程的推荐语法。因此,yield from在协程中不再需要。
但一般来说,在asyncio之外,yield from <子生成器>在迭代子生成器时仍有一些其他用途,如前面的回答中所述。
其他回答
一个简短的例子将帮助您理解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]]])))
Yield from生成一个生成器,直到生成器为空,然后继续执行以下代码行。
e.g.
def gen(sequence):
for i in sequence:
yield i
def merge_batch(sub_seq):
yield {"data": sub_seq}
def modified_gen(g, batch_size):
stream = []
for i in g:
stream.append(i)
stream_len = len(stream)
if stream_len == batch_size:
yield from merge_batch(stream)
print("batch ends")
stream = []
stream_len = 0
运行这个程序会得到:
In [17]: g = gen([1,2,3,4,5,6,7,8,9,10])
In [18]: mg = modified_gen(g, 2)
In [19]: next(mg)
Out[19]: {'data': [1, 2]}
In [20]: next(mg)
batch ends
Out[20]: {'data': [3, 4]}
In [21]: next(mg)
batch ends
Out[21]: {'data': [5, 6]}
In [22]: next(mg)
batch ends
Out[22]: {'data': [7, 8]}
In [23]: next(mg)
batch ends
Out[23]: {'data': [9, 10]}
In [24]: next(mg)
batch ends
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Input In [24], in <cell line: 1>()
----> 1 next(mg)
StopIteration:
因此,yield from可以从另一个生成器获取输出,做一些修改,然后将自己的输出作为生成器本身提供给其他生成器。
在我看来,这是yield from的主要用例之一
在什么情况下“yield from”是有用的?
你有这样一个循环的每一种情况:
for x in subgenerator:
yield x
正如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)
更重要的是,在生成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)
现在您决定将这些循环分解到单独的生成器中。没有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)
为什么将其与微线程进行比较?
我认为PEP中的这一节讨论的是每个生成器都有自己的独立执行上下文。再加上分别使用yield和__next__()在生成器-迭代器和调用者之间切换执行,这类似于线程,其中操作系统不时切换执行线程,以及执行上下文(堆栈,寄存器,…)。
这样做的效果也是类似的:生成器-迭代器和调用者都在执行状态下同时进行,它们的执行是交错的。例如,如果生成器执行某种计算,而调用者打印出结果,那么只要结果可用,您就会看到结果。这是并发的一种形式。
不过,这个类比并不是什么特别的东西,而是Python中生成器的一般属性。
Yield将产生单个值到集合中。
从一个集合到另一个集合,让它变平。
请看这个例子:
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)
在控制台,你会看到:
A
B
C
A
B
C
A
B
C
简单地说,yield from为迭代器函数提供了尾部递归。