说明
这里的问题是创建i
函数时未保存的值f
。而是查询何时调用f
的值。i
如果您考虑一下,这种行为是很合理的。实际上,这是功能起作用的唯一合理方式。假设您有一个访问全局变量的函数,如下所示:
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
当您阅读此代码时,您当然会希望它显示“ bar”,而不是“ foo”,因为在global_var
声明函数后,的值已更改。您自己的代码中发生了同样的事情:在您调用时f
,的值i
已更改并设置为2
。
解决方案
实际上,有很多方法可以解决此问题。以下是一些选择:
i
通过将其用作默认参数来强制早期绑定
与闭包变量(如i
)不同,定义函数时会立即对默认参数进行求值:
for i in range(3):
def f(i=i): # <- right here is the important bit
return i
functions.append(f)
深入了解其工作方式/原因:函数的默认参数存储为函数的属性;因此,快照的当前值i
并保存。
>>> 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
可以更改的变量。我们可以通过创建另一个永不更改的变量来解决此问题,最简单的方法是闭包:
def 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
使您可以将参数附加到现有函数。在某种程度上,它也是一种功能工厂。
import 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)
注意:仅当您为变量分配新值时,这些解决方案才有效。如果修改存储在变量中的对象,您将再次遇到相同的问题:
>>> 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]
请注意,i
即使我们将其变成默认参数,它仍然有多大变化!如果你的代码发生变异 i
,那么你就必须绑定一个副本的i
你的功能,如下所示:
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())