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
相关产品推荐
相关产品推荐

