提问人:Mnng 提问时间:11/2/2023 更新时间:11/2/2023 访问量:51
什么是“预抽发电机协程”?
What is a "pre-pumped generator coroutine"?
问:
我在 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 是将替换装饰函数的函数。签名意味着它可以接受任意数量的位置和关键字参数,正如我们稍后将看到的,这些参数的唯一用途是转发到原始函数中(顺便说一句,这是大多数装饰器所做的)
lambda *a, **kw: ...
*a, **kw:
这个内部 lambda 的主体是一个包含两个表达式的列表,后跟一个索引。
[ci := c(*a, **kw), next(ci)][0]
- 列表的第一个元素是
ci := c(*a, **kw)
-- 在这里,该部分是实现对原始修饰函数(代码段中 的函数 WITH 开头的函数)的实际调用。
c(*a, **kw)
def c: ...
- 该部件获取该调用的返回值,并将其存储在临时变量 () 中。相同的值也用作列表位置 0 处的项目(因此,它是内部 lambda 将返回的值)
ci :=
ci
- 另请注意,这里的变量是 outter lamba 的参数名称 - - 当片段从视频转录到这个问题时,它被错误地重命名为。(正确):指装饰功能。在代码段中,它也被命名为 ,但它可以有任何名称。
c
c
x
c
c
- 在这里,该部分是实现对原始修饰函数(代码段中 的函数 WITH 开头的函数)的实际调用。
- 内部 lambda 列表的第二个元素,将推进存储在变量中的生成器,从而“抽取生成器”。由于列表中的表达式按其出现的顺序执行,因此此调用始终在创建生成器后发生,并存储在列表的第一个(索引 0)位置和变量上。
next(ci)
ci
,
ci
- 最后,index 将简单地选择前面列表中的 select one item 作为函数的返回值(内部 lambda,它取代了修饰的函数)。
[0]
[ci: = ..., next...]
def c():...
- 这个索引在原始代码中,是作者的错误 - 它应该指示要返回该列表的第一个元素。
[-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
评论
...
*
评论