Python在循环中创建函数以捕获循环变量
这里发生了什么呢?我正在尝试创建一个函数的列表:
def f(a,b):
return a*b
funcs = []
for i in range(0,10):
funcs.append(lambda x:f(i,x))
但是这个结果和我预期的不一样。我本来希望这个列表能这样工作:
funcs[3](3) = 9
funcs[0](5) = 0
可是在这个列表里的所有函数看起来都是一样的,而且都把固定值设置成了9:
funcs[3](3) = 27
funcs[3](1) = 9
funcs[2](6) = 54
有什么想法吗?
5 个回答
10
其实每个 lambda 里只有一个 i
,这和你想的可能不一样。这是一个常见的误解。
想要达到你想要的效果,有一种方法是:
for i in range(0,10):
funcs.append(lambda x, i=i: f(i, x))
这样你就在每个 lambda 的闭包里创建了一个默认参数 i
,并把当前循环变量 i
的值绑定到它上面。
15
没错,这就是我们常说的“作用域问题”(其实是一个绑定比你想的晚的问题,但大家一般都这么称呼它)。你已经得到了两个最好的(因为最简单的)解决方案——一个是“假默认” i=i
的方法,另一个是 functools.partial
,所以我这里就给你介绍第三个经典的解决方案,叫做“工厂 lambda”:
for i in range(0,10):
funcs.append((lambda i: lambda x: f(i, x))(i))
个人来说,如果没有风险让 funcs
中的函数意外地用两个参数调用而不是一个,我会选择 i=i
。不过,当你需要的功能比仅仅预先绑定一个参数更丰富时,工厂函数的方法也是值得考虑的。
20
在Python中,lambda表达式是一种闭包……你传给它的参数在lambda被执行之前是不会被计算的。到那时,i的值是9,因为你的循环已经结束了。
你想要的效果可以通过functools.partial来实现。
import functools
def f(a,b):
return a*b
funcs = []
for i in range(0,10):
funcs.append(functools.partial(f,i))