You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中两种Lambda写法差异及生成器函数问题解析

背景

我们需要实现Python生成器函数repeated,它生成的函数是对单参数函数f重复应用的结果:第一个生成的是应用f0次的恒等函数,第二个是应用1次的函数,以此类推。对应的doctest如下:

def repeated(f):
    """
    >>> double = lambda x: 2 * x
    >>> funcs = repeated(double)
    >>> identity = next(funcs)
    >>> double = next(funcs)
    >>> quad = next(funcs)
    >>> oct = next(funcs)
    >>> quad(1)
    4
    >>> oct(1)
    8
    >>> [g(1) for _, g in
    ...  zip(range(5), repeated(lambda x: 2 * x))]
    [1, 2, 4, 8, 16]
    """

最初尝试的实现代码:

def repeated(f):
    g = lambda x: x
    while 1:
        yield g
        g = lambda x: f(g(x))

问题1

上述代码中,g = lambda x: f(g(x))会触发最大递归深度错误,但改成g = (lambda h: lambda x: f(h(x)))(g)就能正常运行,这是为什么?两种写法有什么差异?

解答

核心原因是Python中lambda对变量的延迟绑定特性:

  • 第一种写法g = lambda x: f(g(x))里,lambda内部的g不会在定义时就确定值,而是等到lambda被调用时才去查找当前g的指向。当后续循环中g被不断重新赋值,之前生成的所有lambda都会引用最新的g,最终调用时会陷入无限递归(每个lambda调用时都会调用最新的g,而最新的g又指向自身逻辑)。
  • 第二种写法通过立即绑定解决了这个问题:外层lambda h: ...会立即接收当前的g作为参数h,内层lambda引用的是外层lambda的局部变量h,这个h的值在调用外层lambda时就被固定为当时的g,不会被后续g的赋值修改。相当于给当前的g拍了个快照,把它的行为固定下来,避免了变量变化带来的绑定混乱。

问题2

下面这段代码几乎能通过doctest,但quad(1)测试失败:预期返回4,实际返回8。把yield lambda x: g(x)改成yield g后就能通过测试,lambda x: g(x)和g有什么差异?

对应的代码:

def repeated(f):
    g = lambda x: x
    while 1:
        yield lambda x: g(x)
        g = (lambda h: lambda x: f(h(x)))(g)

解答

差异依然来自变量绑定的时机:

  • yield g直接返回的是当前的g函数对象,这个对象的行为已经被固定(比如第一次yield的是恒等函数,第二次是应用一次f的函数),后续g的重新赋值不会影响已经返回的这个函数。
  • yield lambda x: g(x)返回的是一个新的lambda,它同样是延迟绑定g的。当后续循环中g被更新为新的函数(比如从恒等函数变成double,再变成quad),之前返回的lambda在被调用时,会使用最新的g值。比如quad变量接收的是第三次yield的lambda,此时g已经被更新为对应oct的函数,所以调用quad(1)时,实际执行的是最新的g(1),得到8而非预期的4。

内容的提问来源于stack exchange,提问作者Lurx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 13:17:32