我在 PyCon 2023 上看到了 James Powell 的这个演讲,他提到了“预抽生成器协程”的概念(15:47)。
这是他提到的代码:
@lambda x: lambda *a, **kw: [ci := c(*a, **kw), next(ci)][-1]
def c(*, mode):
data = yield
根据我的研究,“泵送”与“启动”生成器相关(或相同),这是在新初始化的生成器上调用 next() 以使其准备好接收数据的过程。
我想知道,这个概念是什么?是否有人可以解释这个函数是如何运行的?
免责声明:讲师在用粗体写下“极其复杂”后立即展示了此代码,并按照片段的显示大声而清晰地说“不要使用此代码”。
@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
那么,让我们逐一回顾一下各个部分:首先,
@
之后以及函数或类之前的任何内容都是装饰器 - 并且它需要是一个可调用的,将函数作为唯一参数。
看那里,我们有一个
@lambda c: ...
- 因此,它是一个函数,它将接受装饰函数作为参数,并返回一个新函数。 (可以是任何可调用的,但为了简单起见,我们坚持使用“函数”一词)
lambda 函数的主体必须是单个表达式。在本例中,它是另一个 lambda,即
lambda *a, **kw: ...
- 这意味着这个内部 lambda 是将替换装饰函数的函数。 *a, **kw:
签名意味着它可以接受任意数量的位置参数和关键字参数,正如我们很快就会看到的,这些参数的唯一用途是将 then 转发到原始函数中(顺便说一句,这就是大多数装饰器所做的)
这个内部 lambda 的主体是一个包含两个表达式的列表,后跟一个索引
[ci := c(*a, **kw), next(ci)][0]
。
ci := c(*a, **kw)
-
c(*a, **kw)
部分是实现对原始修饰函数的实际调用(代码片段中以def c: ...
开头的函数)。ci :=
部分获取该调用的返回值,并将其存储在临时变量(ci
)中。相同的值也用作列表位置 0 处的项目(因此,它将由内部 lambda 返回)c
变量是外部 lba 的参数名称 - c
- 当片段从视频转录到此问题时,它被错误地重命名为 x
。 (c
正确):指的是修饰函数。在代码片段中,它也被命名为 c
,但它可以有任何名称。next(ci)
将推进存储在 ci
变量中的生成器,从而“泵送生成器”。由于列表内的表达式按照它们出现的顺序执行,并使用 ,
作为表达式分隔符,因此此调用始终在创建生成器之后发生,并存储在列表的第一个(索引 0)位置和ci
变量。[0]
索引将简单地选择前面列表[ci: = ..., next...]
中的一项作为函数的返回值(内部lambda,它取代了装饰函数def c():...
)。
[-1]
,是作者的错误 - 它应该是 [0]
来指示要返回该列表的第一个元素。最终返回的值是原始函数返回的值,但在调用
next(...)
之后。如果修饰函数是一个生成器函数,就像在本例中一样,它会被初始化到第一个 yield
,或者用这里使用的术语“泵送”。
不使用它的原因很明显:虽然它只需要一行代码,但需要大约 30 行英文文本来解释正在发生的事情(顺便说一句,这也是视频中讲师的观点,尽管他承认他实际上确实在某个时候使用过这段代码)