Creating functions in a loop
我尝试在循环内部创建函数:
1 2 3 4 5 6 7 8 9 | functions = [] for i in range(3): def f(): return i # alternatively: f = lambda: i functions.append(f) |
问题是所有功能最终都是相同的。这三个函数都返回2,而不是返回0、1和2:
1 2 3 | print([f() for f in functions]) # expected output: [0, 1, 2] # actual output: [2, 2, 2] |
号
为什么会发生这种情况,我应该怎么做才能得到分别输出0、1和2的3个不同函数?
您遇到了后期绑定的问题——每个函数查找
通过强制早期绑定很容易修复:将
1 2 | def f(i=i): return i |
默认值(
如果你担心
1 2 3 4 | def make_f(i): def f(): return i return f |
号
在您的循环中,使用
解释
这里的问题是,在创建函数
如果你仔细想想,这种行为是完全有意义的。事实上,这是功能工作的唯一合理方式。假设您有一个访问全局变量的函数,如下所示:
1 2 3 4 5 6 7 | global_var = 'foo' def my_function(): print(global_var) global_var = 'bar' my_function() |
当您阅读此代码时,您当然希望它打印"bar",而不是"foo",因为在声明函数后EDOCX1的值(19)发生了更改。在您自己的代码中也发生了同样的事情:当您调用
实际上有很多方法可以解决这个问题。以下是几个选项:
- 强制早期绑定
i ,将其用作默认参数与闭包变量(如
i )不同,在定义函数时,将立即计算默认参数:1
2
3
4
5for i in range(3):
def f(i=i): # <- right here is the important bit
return i
functions.append(f)。
为了深入了解这是如何工作的/为什么工作的:函数的默认参数存储为函数的一个属性;因此,
i 的当前值被快照并保存。1
2
3
4
5
6
7
8
9>>> i = 0
>>> def f(i=i):
... pass
>>> f.__defaults__ # this is where the current value of i is stored
(0,)
>>> # assigning a new value to i has no effect on the function's default arguments
>>> i = 5
>>> f.__defaults__
(0,) - 使用函数工厂捕获闭包中
i 的当前值问题的根源在于
i 是一个可以改变的变量。我们可以通过创建另一个保证永远不会改变的变量来解决这个问题,最简单的方法是关闭:1
2
3
4
5
6
7
8def f_factory(i):
def f():
return i # i is now a *local* variable of f_factory and can't ever change
return f
for i in range(3):
f = f_factory(i)
functions.append(f)。
- 用
functools.partial 将i 的现值绑定到f 上。functools.partial 允许将参数附加到现有函数。在某种程度上,它也是一种功能工厂。1
2
3
4
5
6
7
8import functools
def f(i):
return i
for i in range(3):
f_with_i = functools.partial(f, i) # important: use a different variable than"f"
functions.append(f_with_i)。
警告:这些解决方案仅在为变量指定新值时有效。如果修改存储在变量中的对象,将再次遇到相同的问题:
1 2 3 4 5 6 7 | >>> i = [] # instead of an int, i is now a *mutable* object >>> def f(i=i): ... print('i =', i) ... >>> i.append(5) # instead of *assigning* a new value to i, we're *mutating* it >>> f() i = [5] |
注意,即使我们把
- 埃多克斯1〔35〕
- 埃多克斯1〔36〕
- 埃多克斯1〔37〕