Python Lambda表达式递归深度超限问题及解释器原理疑问
我写了一个Python函数div_by_primes_under,用来判断数值是否能被n以内的质数整除。用简单Lambda实现时触发了maximum recursion depth exceeded in comparison错误,代码如下:
def div_by_primes_under(n): checker = lambda x: False i = 2 while i <= n: if not checker(i): # checker = (lambda f, i: lambda x: x % i == 0 or f(x))(checker, i) checker = lambda x: (x % i == 0 or checker(x)) i = i + 1 return checker print(div_by_primes_under(10)(12)) print(div_by_primes_under(10)(121))
错误信息如下:
Traceback (most recent call last): File "hw2.py", line 106, in <module> print(div_by_primes_under(10)(12)) File "hw2.py", line 102, in <lambda> checker = lambda x: (x % i == 0 or checker(x)) File "hw2.py", line 102, in <lambda> checker = lambda x: (x % i == 0 or checker(x)) File "hw2.py", line 102, in <lambda> checker = lambda x: (x % i == 0 or checker(x)) [Previous line repeated 996 more times] RecursionError: maximum recursion depth exceeded in comparison
改用注释行中的嵌套Lambda表达式后,程序可正常运行。我想了解该问题是否与Python解释器的原理相关。
问题根源:Python闭包的延迟绑定特性
这个问题完全和Python的闭包变量绑定机制、lambda的特性有关,核心两点:
1. 简单Lambda的变量引用陷阱
你写的checker = lambda x: (x % i == 0 or checker(x))里,lambda并没有立即捕获当前i和checker的具体值,而是保存了对这些变量的引用。只有当你调用这个lambda时,它才会去外部作用域(也就是div_by_primes_under函数的作用域)查找这两个变量的最新值。
循环结束后,i已经变成了11(因为循环到i<=10,最后一次i+1后跳出循环),而checker最终指向的是最后一次循环创建的lambda。当你调用这个lambda时,它会执行x%11 == 0 or checker(x)——这里的checker就是它自己,于是触发了无限递归,直到超出Python的递归深度限制,抛出错误。
2. 嵌套Lambda的解决逻辑
注释里的checker = (lambda f, i: lambda x: x % i == 0 or f(x))(checker, i)用了一个立即执行的Lambda(IIFE),把当前的checker(旧的检查函数)和i(当前的质数)作为参数传入。
Python的函数参数是按值传递(对于不可变对象,实际传递的是值的副本),这个外层Lambda会立即捕获传入的f和i的当前值,内层Lambda引用的是外层Lambda作用域里的f和i,而不是外部循环的变量。这样每次循环创建的内层Lambda,都会绑定当前循环的质数i和之前的checker函数,不会被后续循环的变量更新影响,调用时就会按预期依次检查每个质数,不会出现无限递归。
内容的提问来源于stack exchange,提问作者Asatsuki Mio

