Python闭包与Lambda作用域陷阱:迟绑定原理及解决方案
本文深入解析Python中闭包与Lambda表达式常见的作用域陷阱。由于Python采用LEGB查找规则且闭包具有迟绑定特性,循环变量在内部函数调用时才被查询,导致所有函数返回相同结果。文章通过生成器、列表推导及filter迭代器三个典型案例,详细演示了问题成因,并提供了将闭包作用域转换为局部作用域的有效修复方案。
Python闭包与lambda的作用域
lambda写法
def fun():
for i in range(3):
yield lambda x : x * i
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))闭包的写法
def fun():
result = []
for i in range(3):
def demo(x):
return x * i
result.append(demo)
return result
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))上述两种写法的执行结果均为2、4、6,但按照直觉预期,结果应当是0、2、6。
问题原因:
根本原因在于Python的变量查找规则LEGB(Local、Enclosing、Global、Built-in)。在上述示例中,变量i位于闭包作用域(Enclosing scope)。Python的闭包机制采用迟绑定(Late Binding),这意味着内部函数中引用的变量i的值,并非在定义时确定,而是在内部函数实际被调用时才去查找并获取。
解决办法
将闭包作用域转换为局部作用域。
lambda写法
def fun():
for i in range(3):
yield lambda x, i = i: x * i
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))闭包写法
def fun():
result = []
for i in range(3):
def demo(x, i=i):
return x * i
result.append(demo)
return result
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))以上两种修改后的输出结果为0、2、6。
另一种情况:
def fun():
for i in range(3):
yield lambda x : x * i
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))输出结果依然为2、4、6。
问题原因
fun()方法返回的是一个生成器(或迭代器),它并未立即执行,而是在每次遍历调用时才执行具体逻辑。
在遍历结束后执行打印操作时,变量i使用的是最后一次循环迭代后的值。如果将lambda视为闭包方法,变量i的值依然来源于闭包作用域(无局部变量覆盖)。
python当中的坑(闭包与lambda)
先来看一个栗子
def create():
return [lambda x:i*x for i in range(5)]
for i in create():
print(i(2))结果:
8
8
8
8
8
create函数的返回值是一个列表,列表中的每个元素都是一个函数,其功能是将输入参数x乘以倍数i。预期的结果应该是0、2、4、6、8,但实际输出了五个8,这确实出乎意料。
由于出现这个陷阱时经常使用了lambda,所以可能会误认为是lambda的问题,但lambda表示不愿背这个锅。问题的本质在于Python中的属性查找规则LEGB(Local、Enclosing、Global、Built-in)。在上面的例子中,i位于闭包作用域(Enclosing scope),而Python的闭包是迟绑定,这意味着闭包中用到的变量的值,是在内部函数被调用时查询得到的。
解决办法也很简单,那就是变闭包作用域为局部作用域。
def create():
return [lambda x, i=i:i*x for i in range(5)]
for i in create():
print(i(2))换种写法:
def create():
a = []
for i in range(5):
def demo(x, i=i):
return x*i
a.append(demo)
return a
for i in create():
print(i(2))以上两种写法是一样的
结果:
0 2 4 6 8
再来一个问题相似的栗子
代码很简单:(声明:python3问题)
nums = range(2,20)
for i in nums:
nums = filter(lambda x: x==i or x%i, nums)
print(list(nums))结果:
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
同样按照正常的逻辑结果应该为:
[2, 3, 5, 7, 11, 13, 17, 19]
问题产生的原因:
- 在python3当中filter()函数返回的是一个迭代器,因此并没有做真正的执行,而是在每次调用的时候执行(python2中filter()返回的值列表,无此现象)
- 在遍历后执行打印时,现在执行循环当中的函数,同上面一个栗子的问题,i这个变量使用的是最后调用时的值,与以上栗子不同的是以上栗子用的是内嵌作用域的值,而这个栗子用的是全局i的值
修改代码:
nums = range(2,20)
for i in nums:
nums = filter(lambda x,i=i: x==i or x%i, nums)
print(list(nums))结果:
[2, 3, 5, 7, 11, 13, 17, 19]
以上为个人经验,希望能给大家一个参考,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









