什么是“预抽发电机协程”?

What is a "pre-pumped generator coroutine"?

提问人:Mnng 提问时间:11/2/2023 更新时间:11/2/2023 访问量:51

问:

我在 PyCon 2023 上看到了 James Powell 的演讲,他提到了“预抽水发电机协程”的概念(15:47)。

这是他提到的代码:

@lambda x: lambda *a, **kw: [ci := c(*a, **kw), next(ci)][-1]
def c(*, mode):
    data  = yield

根据我的研究,“抽水”与“启动”生成器相关(或相同),这是在新初始化的生成器上调用 next() 以使其准备好接收数据的过程。

我想知道,这个概念是什么,如果有人可以解释这个函数是如何运行的?

Python 生成器 协程

评论

3赞 C.Nivs 11/2/2023
这感觉就像一段代码,不久之后大卫·比兹利(David Beazley)说:“......如果你想成为额外的恶魔”
1赞 Charles Duffy 11/2/2023
启动的概念比:)所示的代码要简单得多——代码是......好吧,我会称它不太符合 PEP-20,也许到了不“实用”的地步(在 stackoverflow.com/help/dont-ask 第二段中给出的含义)
0赞 Kelly Bundy 11/2/2023
如果那一行代码没有两个错误,那将会有所帮助。他看起来很邋遢。
0赞 deceze 11/2/2023
@Kelly 根据定义,任何编写此类代码并继续谈论它的人都是可疑的。
0赞 Kelly Bundy 11/2/2023
(更正:他只有一个错误,你添加了另一个)

答:

3赞 jsbueno 11/25/2023 #1

免责声明:讲师在用粗体写完“极端复杂性”后立即演示了此代码,并按照显示的片段大声而清晰地显示“不要使用此代码”。

@lambda x: lambda *a, **kw: [ci := c(*a, **kw), next(ci)][-1]
def c(*, mode):
    data  = yield

因此,首先,如上面评论中@kellybundy所述,此代码中有两个更正 - 让我们在继续之前将其正确处理:

@lambda c: lambda *a, **kw: [ci := c(*a, **kw), next(ci)][0]
def c(*, mode):
    data  = yield

因此,让我们来看看各个部分:首先,函数或类后面和前面的任何内容都是装饰器 - 它需要是一个可调用对象,它将函数作为其唯一参数。@

  1. 在那里,我们有一个 - 因此,它是一个函数,它将装饰的函数作为参数,并返回一个新函数。(可以是任何可调用的,但为了简单起见,让我们坚持使用“函数”一词)@lambda c: ...

  2. lambda 函数的主体必须是单个表达式。在本例中,它是另一个 lambda,去 - 这意味着这个内部 lambda 是将替换装饰函数的函数。签名意味着它可以接受任意数量的位置和关键字参数,正如我们稍后将看到的,这些参数的唯一用途是转发到原始函数中(顺便说一句,这是大多数装饰器所做的)lambda *a, **kw: ...*a, **kw:

  3. 这个内部 lambda 的主体是一个包含两个表达式的列表,后跟一个索引。 [ci := c(*a, **kw), next(ci)][0]

    1. 列表的第一个元素是ci := c(*a, **kw) -
      1. 在这里,该部分是实现对原始修饰函数(代码段中 的函数 WITH 开头的函数)的实际调用。c(*a, **kw)def c: ...
      2. 该部件获取该调用的返回值,并将其存储在临时变量 () 中。相同的值也用作列表位置 0 处的项目(因此,它是内部 lambda 将返回的值)ci := ci
      3. 另请注意,这里的变量是 outter lamba 的参数名称 - - 当片段从视频转录到这个问题时,它被错误地重命名为。(正确):指装饰功能。在代码段中,它也被命名为 ,但它可以有任何名称。ccxcc
    2. 内部 lambda 列表的第二个元素,将推进存储在变量中的生成器,从而“抽取生成器”。由于列表中的表达式按其出现的顺序执行,因此此调用始终在创建生成器后发生,并存储在列表的第一个(索引 0)位置和变量上。next(ci)ci, ci
    3. 最后,index 将简单地选择前面列表中的 select one item 作为函数的返回值(内部 lambda,它取代了修饰的函数)。[0][ci: = ..., next...]def c():...
      1. 这个索引在原始代码中,是作者的错误 - 它应该指示要返回该列表的第一个元素。[-1][0]

最终返回的值 so,是原始函数返回的任何值,但在 after 上被调用。如果修饰的函数是生成器函数,如本例所示,则它被初始化到第一个,或者用此处使用的术语“抽水”。 不使用它的原因很明显:虽然它只需要一行代码,但需要 ~30 行英文文本来解释正在发生的事情(顺便说一句,这是视频中讲师的重点,尽管他承认他确实在某个时候使用了这段代码next(...)yield)

最后,这可以用几行写成一个完整的装饰器,实际上可以使用:pump

from functools import wraps

def prepumped(func):
    @wraps func:
    def wrapper(*args, **kwargs):
         gen = func(*args, **kwargs)
         next(gen)
         return gen
    return wrapper

@prepumped
def c(*, mode):
    data = yield

评论

1赞 jsbueno 11/25/2023
对不起 - 我已经通过文章来指示“这里是一些我们不必关心的东西”,并且在重新输入它时在最终示例函数中做了同样的事情。但这是一个语法错误,所以我正在恢复 ,即使它的签名对示例无关紧要,也一样。...*