Python 在循环中创建函数
我试图在循环中创建函数:Python 在循环中创建函数,python,function,Python,Function,我试图在循环中创建函数: functions = [] for i in range(3): def f(): return i # alternatively: f = lambda: i functions.append(f) 问题是所有函数最终都是相同的。这三个函数都返回2,而不是返回0、1和2: print([f() for f in functions]) # expected output: [0, 1, 2] # actual out
functions = []
for i in range(3):
def f():
return i
# alternatively: f = lambda: i
functions.append(f)
问题是所有函数最终都是相同的。这三个函数都返回2,而不是返回0、1和2:
print([f() for f in functions])
# expected output: [0, 1, 2]
# actual output: [2, 2, 2]
为什么会发生这种情况,我应该怎么做才能得到分别输出0、1和2的3个不同函数?您遇到了延迟绑定的问题——每个函数都尽可能晚地查找
I
(因此,当在循环结束后调用时,I
将设置为2
)
通过强制提前绑定可以轻松修复:将def()更改为def(i=i):
如下:
def f(i=i):
return i
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
默认值(在i=i
中右边的i
是参数名i
的默认值,而在i=i
中左边的i
是在def
时间查找的,而不是在调用时间查找的,因此本质上它们是专门查找早期绑定的一种方法
如果您担心f
会得到一个额外的参数(因此可能会被错误地调用),有一种更复杂的方法涉及将闭包用作“函数工厂”:
在循环中使用f=make_f(i)
而不是def
语句。解释
这里的问题是,创建函数f
时,不会保存i
的值。相反,f
在调用时查找i
的值
如果你仔细想想,这种行为是完全有道理的。事实上,这是函数工作的唯一合理方式。假设您有一个访问全局变量的函数,如下所示:
def f(i=i):
return i
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
当您阅读此代码时,您当然希望它打印“bar”,而不是“foo”,因为函数声明后,global\u 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
是一个可以更改的变量。我们可以通过创建另一个保证永远不变的变量来解决这个问题,最简单的方法是闭包:
- 使用
functools.partial
将i
的当前值绑定到f
用于将参数附加到现有函数。在某种程度上,它也是一种功能工厂
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(i=i.copy()):
f=f\u工厂(i.copy())
f_with_i=functools.partial(f,i.copy())
为了补充@Aran Fey的优秀答案,在第二个解决方案中,您可能还希望修改函数中的变量,该变量可以通过关键字非局部
完成:
def f_factory(i):
def f(offset):
nonlocal i
i += offset
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)
print(f(10))
作为对自己的提醒:你怎么知道如何解决这些问题?@alwbtc这主要是经验,大多数人在某个时候都会独自面对这些问题。你能解释一下为什么它会起作用吗?(您在循环中生成的回调上保存了我,参数始终是循环的最后一个参数,所以谢谢!)
def f_factory(i):
def f(offset):
nonlocal i
i += offset
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)
print(f(10))