什么是“预抽生成器协程”?

问题描述 投票:0回答:1

我在 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 generator coroutine
1个回答
0
投票

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

@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 *a, **kw: ...
    - 这意味着这个内部 lambda 是将替换装饰函数的函数。
    *a, **kw: 
    签名意味着它可以接受任意数量的位置参数和关键字参数,正如我们很快就会看到的,这些参数的唯一用途是将 then 转发到原始函数中(顺便说一句,这就是大多数装饰器所做的)

  3. 这个内部 lambda 的主体是一个包含两个表达式的列表,后跟一个索引

     [ci := c(*a, **kw), next(ci)][0]

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

最终返回的值是原始函数返回的值,但在调用

next(...)
之后。如果修饰函数是一个生成器函数,就像在本例中一样,它会被初始化到第一个
yield
,或者用这里使用的术语“泵送”。 不使用它的原因很明显:虽然它只需要一行代码,但需要大约 30 行英文文本来解释正在发生的事情(顺便说一句,这也是视频中讲师的观点,尽管他承认他实际上确实在某个时候使用过这段代码

© www.soinside.com 2019 - 2024. All rights reserved.