【问题标题】:Lambda in a loop [duplicate]循环中的 Lambda [重复]
【发布时间】:2022-01-19 12:54:09
【问题描述】:

考虑如下代码sn-p:

# 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 ?
}

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

【问题讨论】:

  • 诸如将外部变量绑定到部分 lambda 之类的问题是 ruby​​ 并非无处不在的副作用。

标签: python loops anonymous-function


【解决方案1】:

您需要为创建的每个函数绑定 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)

【讨论】:

  • 我没有意识到 lambdas 中允许使用默认值。将带有默认参数的 lambda 作为关键字参数传递看起来很奇怪:command = lambda path = path: selected(path)
  • 是的,没错,但是如果声明了函数的签名,我该怎么办,想象一下 tkinter 的绑定或跟踪函数的情况。
【解决方案2】:

这是由于 d 被绑定的点。 lambda 函数都指向 variable d 而不是它的当前 value,因此当您在下一次迭代中更新 d 时,可以看到此更新你所有的功能。

举个更简单的例子:

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

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

【讨论】:

  • 在使用 def 在 for 循环中定义闭包时是否会出现同样的问题,在 Python 中 for 循环没有自己的范围?
【解决方案3】:

或者,您可以使用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

【讨论】:

  • 这显然是一种更好的方法,我一定会在机会出现时推荐它。 lambda x=x 技巧不仅不直观,它利用了与 a classic gotcha in another context 相同的行为。使用functools.partial 绑定参数是显式的,eta-reduced 也是显式的(这使得它更加干燥)。哦,这个默认参数也可以被覆盖,但不应该是 - 一个潜在的陷阱。
【解决方案4】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-24
    • 1970-01-01
    相关资源
    最近更新 更多