[英]Creating functions (or lambdas) in a loop (or comprehension)
我正在尝试在循环内创建函数:
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]
为什么会发生这种情况,我应该怎么做才能分别获得 output 0、1 和 2 的 3 个不同的函数?
您遇到了后期绑定的问题——每个函数都尽可能晚地查找i
(因此,在循环结束后调用时, i
将设置为2
)。
通过强制早期绑定轻松修复:将def f():
更改为def f(i=i):
如下所示:
def f(i=i):
return i
默认值( i=i
中的右侧i
是参数名称i
的默认值,即i=i
中的左侧i
)是在def
时间而不是在call
时查找的,所以基本上它们是一种专门寻找早期绑定的方法。
如果您担心f
得到一个额外的参数(因此可能会被错误地调用),那么有一种更复杂的方法涉及使用闭包作为“函数工厂”:
def make_f(i):
def f():
return i
return f
并在您的循环中使用f = make_f(i)
而不是def
语句。
这里的问题是创建函数f
时没有保存i
的值。 相反, 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())
要添加到@Aran-Fey 的出色答案,在第二个解决方案中,您可能还希望修改函数内部的变量,这可以使用关键字nonlocal
来完成:
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))
你可以这样尝试:
l=[]
for t in range(10):
def up(y):
print(y)
l.append(up)
l[5]('printing in 5th function')
您必须将每个i
值保存在 memory 中的单独空间中,例如:
class StaticValue:
val = None
def __init__(self, value: int):
StaticValue.val = value
@staticmethod
def get_lambda():
return lambda x: x*StaticValue.val
class NotStaticValue:
def __init__(self, value: int):
self.val = value
def get_lambda(self):
return lambda x: x*self.val
if __name__ == '__main__':
def foo():
return [lambda x: x*i for i in range(4)]
def bar():
return [StaticValue(i).get_lambda() for i in range(4)]
def foo_repaired():
return [NotStaticValue(i).get_lambda() for i in range(4)]
print([x(2) for x in foo()])
print([x(2) for x in bar()])
print([x(2) for x in foo_repaired()])
Result:
[6, 6, 6, 6]
[6, 6, 6, 6]
[0, 2, 4, 6]
我目前正在为此开发一个模块,但您想要做的是创建一个脚本,将函数写入另一个 python 文件,如下所示
try:
FunctionFile = open(outputfolder + "/FunctionFile.", "x")
except:
print("\noutput FunctionFile file exists or error")
FunctionFile = open(outputfolder + "/" + filename + ".py", "a")
FunctionFile.write("import os...\n\n")
functions = [1,2,3,4,5]
for x in functions:
FunctionFile.write("\ndef " + x + '():\n')
FunctionFile.write(" whatever you want it to be"))
FunctionFile.write("\n\n")
这只会工作一次,一旦生成列表,您将需要开发一些代码来检查现有或已删除的功能。
只需修改最后一行
functions.append(f())
编辑:这是因为f
是一个 function - python 将函数视为一等公民,您可以将它们传递给稍后调用的变量。 因此,您的原始代码所做的是将 function 本身附加到列表中,而您想要做的是将 append 的结果添加到列表中,这是上面的行通过调用 function 实现的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.