在循环(或理解)中创建功能(或lambdas)
我正在尝试在循环内部创建功能:
functions = []
for i in range(3):
def f():
return i
functions.append(f)
或者使用lambda:
functions = []
for i in range(3):
functions.append(lambda: i)
问题是所有功能最终都相同。而不是返回0、1和2,所有三个功能都返回2:
print([f() for f in functions])
- 预期输出:
[0,1,2]
- 实际输出:
[2,2,2]
为什么会发生这种情况,我该怎么做才能分别输出3个不同的功能,分别输出0、1和2?
I'm trying to create functions inside of a loop:
functions = []
for i in range(3):
def f():
return i
functions.append(f)
Alternatively, with lambda:
functions = []
for i in range(3):
functions.append(lambda: i)
The problem is that all functions end up being the same. Instead of returning 0, 1, and 2, all three functions return 2:
print([f() for f in functions])
- Expected output:
[0, 1, 2]
- Actual output:
[2, 2, 2]
Why is this happening, and what should I do to get 3 different functions that output 0, 1, and 2 respectively?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
您正在遇到 late binding 的问题 - 每个功能都会尽可能晚(因此,在循环结束后调用时,
I 将设置为
2
)。通过强迫早期绑定而轻松修复:更改
def f():
todef f(i = i):
喜欢这样:默认值(默认值
i
ini = i
是参数namei
的默认值,这是in
i的左手
)查找i
= idef
时间,而不是call
时间,因此,从本质上讲,它们是专门寻找早期绑定的一种方式。如果您担心
f
获取额外的参数(因此可能被错误地称为),则有一种更复杂的方式涉及使用闭合作为“功能工厂”:在您的循环中使用<代码> f = make_f(i)而不是
def
语句。You're running into a problem with late binding -- each function looks up
i
as late as possible (thus, when called after the end of the loop,i
will be set to2
).Easily fixed by forcing early binding: change
def f():
todef f(i=i):
like this:Default values (the right-hand
i
ini=i
is a default value for argument namei
, which is the left-handi
ini=i
) are looked up atdef
time, not atcall
time, so essentially they're a way to specifically looking for early binding.If you're worried about
f
getting an extra argument (and thus potentially being called erroneously), there's a more sophisticated way which involved using a closure as a "function factory":and in your loop use
f = make_f(i)
instead of thedef
statement.这里的说明
是,当创建函数
f
时,i
的值不会保存。相反,f
查找i
的值时,它被称为。如果您考虑一下,这种行为是完全合理的。实际上,这是唯一合理的函数可以使用的方法。想象一下,您具有访问全局变量的函数,例如:
当您读取此代码时,您当然会期望它打印“ bar”,而不是“ foo”,因为
global_var的值声明功能后已更改。同样的事情正在您自己的代码中发生:当您调用
f
时,i
的值已更改并设置为2
。解决方案
实际上有很多解决此问题的方法。以下是一些选项:
i
的强制绑定将其用作默认参数与闭合变量不同(例如
i
),定义函数时会立即评估默认参数:给出一点洞察力,以了解它的工作方式/原因:函数的默认参数被存储为函数的属性;因此,当前
i
的值是快照并保存的。使用功能工厂在闭合中捕获
i
的当前值您问题的根源是
i
是一个可以更改的变量。我们可以通过创建一个保证永远不会改变的另一个变量来解决这个问题 - 最简单的方法是 cloture :使用
使用
funct> Function functools 。
functorools.partials.partials.partial
让您将参数附加到现有函数。从某种意义上说,这也是一种功能工厂。警告:这些解决方案仅在您分配变量的新值时起作用。如果您修改存储在变量中的对象,您将再次遇到相同的问题:
注意
i
如何更改,即使我们将其变成了默认参数!如果您的代码突变i
,则必须将 copyi
绑定到您的功能,例如:f = f_factory(i.copy())
f_with_i = functools.partials.partial(f,i.copy(i.copy)( )
The Explanation
The issue here is that the value of
i
is not saved when the functionf
is created. Rather,f
looks up the value ofi
when it is called.If you think about it, this behavior makes perfect sense. In fact, it's the only reasonable way functions can work. Imagine you have a function that accesses a global variable, like this:
When you read this code, you would - of course - expect it to print "bar", not "foo", because the value of
global_var
has changed after the function was declared. The same thing is happening in your own code: By the time you callf
, the value ofi
has changed and been set to2
.The Solution
There are actually many ways to solve this problem. Here are a few options:
Force early binding of
i
by using it as a default argumentUnlike closure variables (like
i
), default arguments are evaluated immediately when the function is defined:To give a little bit of insight into how/why this works: A function's default arguments are stored as an attribute of the function; thus the current value of
i
is snapshotted and saved.Use a function factory to capture the current value of
i
in a closureThe root of your problem is that
i
is a variable that can change. We can work around this problem by creating another variable that is guaranteed to never change - and the easiest way to do this is a closure:Use
functools.partial
to bind the current value ofi
tof
functools.partial
lets you attach arguments to an existing function. In a way, it too is a kind of function factory.Caveat: These solutions only work if you assign a new value to the variable. If you modify the object stored in the variable, you'll experience the same problem again:
Notice how
i
still changed even though we turned it into a default argument! If your code mutatesi
, then you must bind a copy ofi
to your function, like so:def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())
对于那些使用
lambda
来解决这个问题的人:该解决方案仅替换
lambda:i
bylambda i = i:i
。示例用例:要具有lambda函数,请评估一个变量(而不是推迟)
For those coming to this question using
lambda
:The solution is simply to replace
lambda: i
bylambda i=i: i
.Example use-case: How to have a lambda function evaluate a variable now (and not postponed)
不是答案,而是笔记。
这种现象也可以在循环之外观察到,很清楚发生了什么:
我们(显然是?)得到:
或者可以在问题中查看代码,但通过调用
我们得到了以下内容,并解释了很多:
最终要取决于问题的
i
在问题中的 locap 而不是本地对于程序(以及具有可变状态的语言闭合螺栓固定的语言具有尴尬的角落)。另外,设计Algol60后的Algol风格语言的“有问题”的决定,该语言不必正确地宣布变量无济于事,即这使得阅读Python程序很困难(更不用说大型Python程序在经济上是危险的) 。显然 pep-3104 有一个想法,可以在2006年进行必要的改进,但没有占用。奖金回合:Java
由于我不确定Java会做什么,这里与Java中出现的问题相同。请注意,编译器需要付出额外的努力来检测闭合所看到的变量是可变的(非比赛),并且在外部环境中还是可见的,还是本地或最终的。整个现象不仅是偶然的。
输出
Not an answer, but a note.
This phenomenon can also be observed outside of a loop, where it becomes quite clear what happens:
We (evidently?) get:
Or one can look at the code in the question, but add printout by calling
locals()
andglobals()
We get the following, explaining a lot:
Ultimately it is down to the fact that the
i
of the loop in the question is not local to the loop but global to the program (and that languages with mutable state with closures bolted-one have awkward corners). Also, the 'problematic' decision to design a post-ALGOL60 ALGOL-style language where variables need not be properly declared doesn't help, i.e. it makes reading a Python program quite difficult (not to mention large-ish Python programs economically hazardous). Apparently PEP-3104 had an idea for a necessary improvement in 2006, but it wasn't taken up.Bonus round: Java
As I was unsure what Java would do, here is same problem as it appears in Java. Note that the compiler needs to do extra effort to detect whether the variable the closure sees is mutable (non-final) and visible in an outside context or just local or final. The whole phenomenon is not just accidental.
Output
问题在于
i
被绑定到循环变量,该变量会在每次迭代中都会发生变化。对于此问题有各种各样的解决方案,可读性水平都不同。在下面的4个片段中,底部3个都是正确的,但我认为只有底部是最可读的:The problem is that
i
gets bound to the loop variable, which changes on every iteration. There are various solutions to this problem, with varying levels of readability. Of the 4 snippets below, the bottom 3 are all correct, but I think only the bottom one is the most readable:您可以这样尝试:
You can try like this:
您必须将每个
i
值保存在内存中的单独空间中:You have to save the each of the
i
value in a separate space in memory e.g.:要添加到 @aran-fey的出色答案中,在第二个解决方案中,您可能还希望修改函数内部的变量,可以使用关键字
nonlocal
:
To add onto @Aran-Fey's excellent answer, in the second solution you might also wish to modify the variable inside your function which can be accomplished with the keyword
nonlocal
:只需修改编辑的最后一行
:这是因为
f
是一个函数-Python将功能视为一流的公民,您可以将它们传递到变量中以稍后调用。因此,您的原始代码正在执行的操作是将功能本身附加到列表中,而您想做的是将函数的 result 附加到列表中,这是上面的行通过调用呼叫来实现的功能。just modify the last line for
Edit: This is because
f
is a function - python treats functions as first-class citizens and you can pass them around in variables to be called later on. So what your original code is doing is appending the function itself to the list, while what you want to do is append the results of the function to the list, which is what the line above achieves by calling the function.