python 3.3.2+ python支持创建生成器函数的新语法
yield from <expression>
我已经通过
快速尝试了这个>>> def g():
... yield from [1,2,3,4]
...
>>> for i in g():
... print(i)
...
1
2
3
4
>>>
使用起来似乎很简单,但PEP文档很复杂。我的问题是,与之前的收益率声明相比,还有其他区别吗?感谢。
答案 0 :(得分:20)
对于大多数应用程序,yield from
只是从左侧迭代中按顺序生成所有内容:
def iterable1():
yield 1
yield 2
def iterable2():
yield from iterable1()
yield 3
assert list(iterable2) == [1, 2, 3]
对于看过这篇文章的90%的用户,我猜测这对他们来说已经足够了解。 yield from
只需委托到右侧的iterable。
然而,还有一些更为深奥的发电机情况在这里也很重要。关于发电机的一个鲜为人知的事实是它们可以用作协同程序。这并不常见,但如果您愿意,可以将数据发送到生成器:
def coroutine():
x = yield None
yield 'You sent: %s' % x
c = coroutine()
next(c)
print(c.send('Hello world'))
除此之外:您可能想知道用例是什么(并且您并不孤单)。一个例子是contextlib.contextmanager
装饰器。协同例程也可用于并行化某些任务。我不太了解利用这个地方的太多地方,但谷歌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')
现在,调用者可以捕获该异常并对数据有效负载执行某些操作,从而使其他人受益。
答案 1 :(得分:10)
乍一看,yield from
是一个算法快捷方式:
def generator1():
for item in generator2():
yield item
# do more things in this generator
然后,这大致等同于:
def generator1():
yield from generator2()
# more things on this generator
在英语中:当在迭代中使用时,yield from
在另一个可迭代中发出每个元素,就好像该项来自第一个生成器,从调用第一个生成器的代码的角度来看。
创建它的主要原因是允许容易重构严重依赖于迭代器的代码 - 使用普通函数的代码总是能够以非常小的额外成本将一个函数的块重构为其他函数,然后调用这些函数 - 分割任务,简化阅读和维护代码,并允许更小的代码片段的可重用性 -
所以,像这样的大型函数:
def func1():
# some calculation
for i in somesequence:
# complex calculation using i
# ...
# ...
# ...
# some more code to wrap up results
# finalizing
# ...
可以成为这样的代码,没有缺点:
def func2(i):
# complex calculation using i
# ...
# ...
# ...
return calculated_value
def func1():
# some calculation
for i in somesequence:
func2(i)
# some more code to wrap up results
# finalizing
# ...
然而,当获得迭代器时,表单
def generator1():
for item in generator2():
yield item
# do more things in this generator
for item in generator1():
# do things
要求对于从generator2
消耗的每个项目,首先将运行上下文切换为generator1
,在该上下文中不执行任何操作,并且必须将cotnext切换为generator2
- 当一个值产生一个值时,在获取消耗这些值的实际代码的值之前,还有另一个中间上下文切换到generator1。
避免了来自这些中间上下文切换的收益,如果有很多迭代器被链接,这可以节省相当多的资源:上下文直接从消耗最外层生成器的上下文切换到最里面的生成器,跳过中间的上下文发电机一起,直到内部电池耗尽。
后来,该语言通过中间上下文利用这种“调整”来将这些生成器用作协同例程:可以进行异步调用的函数。有了适当的框架,如https://www.python.org/dev/peps/pep-3156/中所描述的那样,这些协同例程的编写方式是当它们调用需要很长时间才能解析的函数时(由于网络操作或CPU)可以卸载到另一个线程的密集操作) - 使用yield from
语句进行调用 - 然后框架主循环进行排列,以便正确调度被调用的昂贵函数,并重新执行(框架mainloop始终是代码调用协同例程本身)。当昂贵的结果准备就绪时,框架使被调用的协同例程表现得像耗尽的生成器,并且第一个协同例程的执行重新开始。
从程序员的角度来看,就好像代码是直接运行的,没有中断。从过程的角度来看,协同例程在昂贵的呼叫时暂停,而其他(可能并行调用相同的协同例程)继续运行。
因此,有人可能会将一些代码写成网络爬虫的一部分:
@asyncio.coroutine
def crawler(url):
page_content = yield from async_http_fetch(url)
urls = parse(page_content)
...
从asyncio循环调用时,可以同时获取数十个html页面。
Python 3.4将asyncio
模块添加到stdlib,作为此类功能的默认提供程序。它工作得很好,在Python 3.5中,语言中添加了几个新的关键字,以区分协同例程和异步调用以及生成器的使用,如上所述。这些在https://www.python.org/dev/peps/pep-0492/
答案 2 :(得分:1)
这是一个说明它的例子:
>>> def g():
... yield from range(5)
...
>>> list(g())
[0, 1, 2, 3, 4]
>>> def g():
... yield range(5)
...
>>> list(g())
[range(0, 5)]
>>>
yield from
产生了iterable的每个项目,但yield
产生了iterable本身。