我很难把我的大脑控制在PEP 380左右.

  1. 什么情况下yield from是有用的?
  2. classic 用例是什么?
  3. 为什么它与微线程相比?

到目前为止,我使用过生成器,但从未真正使用过协同程序(由PEP-342引入).尽管有一些相似之处,但生成器和协同程序基本上是两个不同的概念.理解协同程序(不仅仅是生成器)是理解新语法的关键.

依我所知,大多数书都让它看起来毫无用处,毫无趣味.


谢谢你精彩的回答,但特别感谢agf和他链接到David Beazley presentations的 comments .

推荐答案

让我们先解决一件事.yield from g等于for v in g: yield vdoes not even begin to do justice的解释是yield from的意思.因为,让我们面对现实吧,如果yield from只是扩展了for循环,那么就不需要向语言中添加yield from,也不需要在Python 2中实现大量新特性.十、

yield from意味着什么establishes a transparent bidirectional connection between the caller and the sub-generator:

  • 该连接是"透明的",因为它也将正确地传播所有内容,而不仅仅是生成的元素(例如,传播异常).

  • 这种连接是"双向的",即数据可以通过生成器fromto发送.

(If we were talking about TCP, 100 might mean "now temporarily disconnect my client's socket and reconnect it to this other server socket".)

顺便说一句,如果你不确定sending data to a generator甚至意味着什么,你需要放下所有的东西,首先阅读coroutines,它们非常有用(与subroutines相比),但不幸的是在Python中不太为人所知.Dave Beazley's Curious Course on Coroutines是一个很好的开始.Read slides 24-33快速入门.

使用Year From从生成器读取数据

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

我们不需要手动迭代reader()次,只需要yield from次.

def reader_wrapper(g):
    yield from g

这是可行的,我们消除了一行代码.也许意图更清楚一点(或者不是).但生活不会改变.

使用yield 率将数据发送到生成器(协同程序)-第1部分

现在让我们做一些更有趣的事情.让我们创建一个名为writer的协同程序,它接受发送给它的数据,并写入套接字、fd等.

def writer():
    """A coroutine that writes data *sent* to it to fd, socket, etc."""
    while True:
        w = (yield)
        print('>> ', w)

现在的问题是,包装器函数应该如何处理向编写器发送数据,以便发送到包装器的任何数据都是transparently发送到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,如果遇到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 coro

yield from透明地处理发送值或将值放入子生成器.

不过,这仍然不能涵盖所有的角落 case .如果外部发电机关闭会发生什么情况?如果子生成器返回值(是的,在Python3.3+中,生成器可以返回值),返回值应该如何传播?That yield from transparently handles all the corner cases is really impressive.yield from真的很神奇,可以处理所有这些案件.

我个人觉得yield from是一个糟糕的关键词 Select ,因为它没有让two-way的本质变得明显.还提出了其他关键字(如delegate个,但被拒绝了,因为向语言中添加一个新关键字比组合现有关键字困难得多.

总之,最好将yield from想象成调用者和子生成器之间的101.

参考资料:

  1. PEP 380.授权给子生成器的语法(Ewing)[v3.3,2009-02-13]
  2. PEP 342 -

Python相关问答推荐

Django关于UniqueBindition的更新

计算每月过go x年的平均值

当变量也可以是无或真时,判断是否为假

KNN分类器中的GridSearchCV

收件箱转换错误- polars.exceptions. ComputeHelp- pandera(0.19.0b3)带有polars

过滤绕轴旋转的螺旋桨

更改Seaborn条形图中的x轴日期时间限制

如何根据另一列值用字典中的值替换列值

ModuleNotFound错误:没有名为Crypto Windows 11、Python 3.11.6的模块

用NumPy优化a[i] = a[i-1]*b[i] + c[i]的迭代计算

pandas:排序多级列

根据列值添加时区

SQLAlchemy bindparam在mssql上失败(但在mysql上工作)

什么是合并两个embrame的最佳方法,其中一个有日期范围,另一个有日期没有任何共享列?

需要帮助重新调整python fill_between与数据点

解决调用嵌入式函数的XSLT中表达式的语法移位/归约冲突

从列表中获取n个元素,其中list [i][0]== value''

搜索按钮不工作,Python tkinter

替换现有列名中的字符,而不创建新列

Gekko中基于时间的间隔约束