Python在循环中创建函数以捕获循环变量

16 投票
5 回答
4814 浏览
提问于 2025-04-15 12:48

这里发生了什么呢?我正在尝试创建一个函数的列表:

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))

撰写回答