python 3.3.2+中yield和from的区别是什么

Erx*_*xin 10 python yield

python 3.3.2+ python支持创建生成器函数的新语法

yield from <expression>
Run Code Online (Sandbox Code Playgroud)

我已经快速尝试了这个

>>> def g():
...     yield from [1,2,3,4]
...
>>> for i in g():
...     print(i)
...
1
2
3
4
>>>
Run Code Online (Sandbox Code Playgroud)

它似乎很简单,但PEP文件很复杂.我的问题是,与之前的收益率声明相比,还有其他区别吗?谢谢.

mgi*_*son 23

对于大多数应用程序,yield from只需按顺序从左侧迭代中生成所有内容:

def iterable1():
    yield 1
    yield 2

def iterable2():
    yield from iterable1()
    yield 3

assert list(iterable2) == [1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

对于看过这篇文章的90%的用户,我猜这对他们来说是足够的解释. yield from只需委托右侧的iterable.


协同程序

然而,还有一些更深奥的发电机情况在这里也很重要.关于发电机的一个鲜为人知的事实是它们可以用作协同程序.这不是很常见,但如果您需要,可以将数据发送到生成器:

def coroutine():
    x = yield None
    yield 'You sent: %s' % x

c = coroutine()
next(c)
print(c.send('Hello world'))
Run Code Online (Sandbox Code Playgroud)

旁白:您可能想知道用例是什么(并且您并不孤单).一个例子是contextlib.contextmanager装饰者.协同例程也可用于并行化某些任务.我不太了解利用它的太多地方,但谷歌app-engine的ndb数据存储API以非常漂亮的方式将它用于异步操作.

现在,让我们假设您将send数据发送到正在从另一个生成器生成数据的生成器...原始生成器如何得到通知?答案是它不在python2.x中,你需要自己包装生成器:

def python2_generator_wapper():
    for item in some_wrapped_generator():
        yield item
Run Code Online (Sandbox Code Playgroud)

至少不是没有很多痛苦:

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 ...
Run Code Online (Sandbox Code Playgroud)

这一切都变得微不足道yield from:

def coroutine_wrapper():
    yield from coroutine()
Run Code Online (Sandbox Code Playgroud)

因为yield from真正委托(一切!)到底层生成器.


返回语义

请注意,有问题的PEP也会更改返回语义.虽然不是直接在OP的问题中,如果你愿意的话,值得快速离题.在python2.x中,您无法执行以下操作:

def iterable():
    yield 'foo'
    return 'done'
Run Code Online (Sandbox Code Playgroud)

这是一个SyntaxError.随着更新yield,上述功能不合法.同样,主要用例是协同程序(见上文).你可以将数据发送到生成器,它可以神奇地工作(也许使用线程?),而程序的其余部分做其他事情.当流量控制传回发电机时,StopIteration将被提升(正常情况下为发电机的末端),但现在StopIteration将具有数据有效负载.这是程序员改为写的:

 raise StopIteration('done')
Run Code Online (Sandbox Code Playgroud)

现在,调用者可以捕获该异常并对数据有效负载执行某些操作以使其他人受益.


jsb*_*eno 10

乍一看,yield from是一个算法快捷方式:

def generator1():
    for item in generator2():
        yield item
    # do more things in this generator
Run Code Online (Sandbox Code Playgroud)

这大致相当于:

def generator1():
    yield from generator2()
    # more things on this generator
Run Code Online (Sandbox Code Playgroud)

在英语中:当在迭代中使用时,yield from从另一个迭代中发出每个元素,就好像该项来自第一个生成器,从调用第一个生成器的代码的角度来看.

其创建的主要原因是允许容易重构严重依赖于迭代器的代码 - 使用普通函数的代码总是能够以非常小的额外成本将一个函数的块重构为其他函数,然后调用它们来划分任务,简化了代码的阅读和维护,并允许更小的代码片段的可重用性 -

所以,像这样的大型功能:

def func1():
    # some calculation
    for i in somesequence:
        # complex calculation using i 
        # ...
        # ...
        # ...
    # some more code to wrap up results
    # finalizing
    # ...
Run Code Online (Sandbox Code Playgroud)

可以成为这样的代码,没有缺点:

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
    # ...
Run Code Online (Sandbox Code Playgroud)

然而,当获得迭代器时,表单

def generator1():
    for item in generator2():
        yield item
    # do more things in this generator

for item in generator1():
    # do things
Run Code Online (Sandbox Code Playgroud)

要求对于消耗的每个项目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)
   ...
Run Code Online (Sandbox Code Playgroud)

从asyncio循环调用时,可以同时获取数十个html页面.

Python 3.4将asyncio模块添加到stdlib作为此类功能的默认提供程序.它工作得很好,在Python 3.5中,语言中添加了几个新的关键字,以区分协同例程和异步调用以及生成器的使用,如上所述.这些在https://www.python.org/dev/peps/pep-0492/中描述


zon*_*ndo 8

这是一个说明它的示例:

>>> def g():
...     yield from range(5)
... 
>>> list(g())
[0, 1, 2, 3, 4]
>>> def g():
...     yield range(5)
... 
>>> list(g())
[range(0, 5)]
>>>
Run Code Online (Sandbox Code Playgroud)

yield from产生可迭代的每个项目,但yield产生可迭代本身。