循环中的 Lambda [复制]

Lambda in a loop [duplicate]

提问人:FunkySayu 提问时间:11/7/2013 最后编辑:martineauFunkySayu 更新时间:8/15/2022 访问量:47495

问:

考虑以下代码片段:

# directorys == {'login': <object at ...>, 'home': <object at ...>}
for d in directorys:
    self.command["cd " + d] = (lambda : self.root.change_directory(d))

我希望创建一个包含两个函数的字典,如下所示:

# Expected :
self.command == {
    "cd login": lambda: self.root.change_directory("login"),
    "cd home": lambda: self.root.change_directory("home")
}

但看起来生成的两个 lambda 函数完全相同:

# Result :
self.command == {
    "cd login": lambda: self.root.change_directory("login"),
    "cd home": lambda: self.root.change_directory("login")   # <- Why login ?
}

我真的不明白为什么。你有什么建议吗?

python 循环匿名 函数

评论


答:

33赞 robbie_c 11/7/2013 #1

这是由于 d 被绑定的点。lambda 函数都指向变量,而不是变量的当前,因此当您在下一次迭代中更新时,所有函数都会看到此更新。dd

举个更简单的例子:

funcs = []
for x in [1,2,3]:
  funcs.append(lambda: x)

for f in funcs:
  print f()

# output:
3
3
3

您可以通过添加一个附加函数来解决这个问题,如下所示:

def makeFunc(x):
  return lambda: x

funcs = []
for x in [1,2,3]:
  funcs.append(makeFunc(x))

for f in funcs:
  print f()

# output:
1
2
3

您还可以修复 lambda 表达式中的作用域

lambda bound_x=x: bound_x

但是,一般来说,这不是好的做法,因为您已经更改了函数的签名。

评论

0赞 bob 4/7/2020
使用 在 for 循环中定义闭包时会出现同样的问题,而在 Python for 循环中没有自己的作用域吗?def
0赞 Guimoute 1/4/2023
@robbie_c 支持第一个示例的有趣行为:如果在创建和附加各种函数的循环之后,调用将抛出一个不存在的 NameError。del xf()x
123赞 None 11/7/2013 #2

您需要为创建的每个函数绑定 d。一种方法是将其作为具有默认值的参数传递:

lambda d=d: self.root.change_directory(d)

现在,函数中的 d 使用该参数,即使它具有相同的名称,并且在创建函数时计算该参数的默认值。为了帮助您了解这一点,请执行以下操作:

lambda bound_d=d: self.root.change_directory(bound_d)

请记住默认值的工作方式,例如对于列表和字典等可变对象,因为您正在绑定对象。

这种带有默认值的参数的习惯用法很常见,但如果您自省函数参数并根据它们的存在确定要执行的操作,则可能会失败。您可以使用另一个闭包来避免该参数:

(lambda d=d: lambda: self.root.change_directory(d))()
# or
(lambda d: lambda: self.root.change_directory(d))(d)

评论

5赞 ArtOfWarfare 1/15/2016
我没有意识到 lambda 中允许默认值。将带有默认参数的 lambda 作为关键字参数传递看起来很奇怪:.command = lambda path = path: selected(path)
0赞 Nikolai Ehrhardt 9/23/2021
是的,这是正确的,但是我能做什么,如果函数的签名被声明,想象一下 tkinter 的绑定或跟踪函数的情况。
3赞 MonsterBat Doppelgänger 10/20/2017 #3

我遇到了同样的问题。所选的解决方案对我有很大帮助,但我认为有必要添加一个精度来使问题的代码具有函数性:在循环之外定义 lambda 函数。顺便说一句,默认值不是必需的。

foo = lambda d: lambda : self.root.change_directory(d)
for d in directorys:
    self.command["cd " + d] = (foo(d))

评论

0赞 Jonathan Mugan 5/6/2022
在我看来最干净和最好的答案。它让我想起了 lambda 演算中的所有东西,lambda x. lambda y. ...
0赞 Guimoute 1/4/2023
@JonathanMugan 它怎么比?self.command["cd " + d] = lambda d=d: self.root.change_directory(d)
17赞 Georgy 7/31/2019 #4

或者,您可以使用 functools.partial 代替 ,在我看来,它具有更简洁的语法。lambda

而不是:

for d in directorys:
    self.command["cd " + d] = (lambda d=d: self.root.change_directory(d))

它将是:

for d in directorys:
    self.command["cd " + d] = partial(self.root.change_directory, d)

或者,这是另一个简单的例子:

numbers = [1, 2, 3]

lambdas = [lambda: print(number) 
           for number in numbers]
lambdas_with_binding = [lambda number=number: print(number) 
                        for number in numbers]
partials = [partial(print, number) 
            for number in numbers]

for function in lambdas:
    function()
# 3 3 3
for function in lambdas_with_binding:
    function()
# 1 2 3
for function in partials:
    function()
# 1 2 3

评论

3赞 Karl Knechtel 6/15/2020
这显然是一种更好的方法,我一定要在机会出现时推荐它。这个伎俩不仅不直观,而且它利用了在另一种情况下是经典陷阱的相同行为。绑定参数 using 是显式的,也是 eta 减少的(这使得它更干燥)。哦,默认参数也可以被覆盖,但不应该被覆盖 - 一个潜在的陷阱。lambda x=xfunctools.partial