Python Lambda循环

Fun*_*ayu 57 python loops anonymous-function

考虑以下代码段:

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

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

# Expected :
self.command == {
    "cd login": lambda: self.root.change_directory("login"),
    "cd home": lambda: self.root.change_directory("home")
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Rog*_*ate 74

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

lambda d=d: self.root.change_directory(d)
Run Code Online (Sandbox Code Playgroud)

现在函数内部的d使用参数,即使它具有相同的名称,并且在创建函数时评估其默认值.为了帮助您看到这个:

lambda bound_d=d: self.root.change_directory(bound_d)
Run Code Online (Sandbox Code Playgroud)

记住默认值的工作原理,例如列表和dicts等可变对象,因为您绑定了一个对象.

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

(lambda d=d: lambda: self.root.change_directory(d))()
# or
(lambda d: lambda: self.root.change_directory(d))(d)
Run Code Online (Sandbox Code Playgroud)

  • 我没有意识到 lambdas 中允许使用默认值。传递一个带有默认参数的 lambda 作为关键字参数看起来很奇怪:`command = lambda path = path: selected(path)`。 (4认同)

rob*_*e_c 20

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

更简单的例子:

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

for f in funcs:
  print f()

# output:
3
3
3
Run Code Online (Sandbox Code Playgroud)

您可以通过添加其他功能来解决此问题,如下所示:

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
Run Code Online (Sandbox Code Playgroud)

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

lambda bound_x=x: bound_x
Run Code Online (Sandbox Code Playgroud)

但是一般来说,这不是一个好习惯,因为你已经改变了你的功能的签名.


Geo*_*rgy 8

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

代替:

for d in directorys:
    self.command["cd " + d] = (lambda d=d: self.root.change_directory(d))
Run Code Online (Sandbox Code Playgroud)

这将是:

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

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

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
Run Code Online (Sandbox Code Playgroud)

  • 这显然是一个更好的方法,我一定会在机会出现时推荐它。“lambda x=x”技巧不仅不直观,它利用了[另一个上下文中的经典陷阱](/sf/ask/79305901/)相同的行为-默认参数)。使用 functools.partial 绑定参数是显式的,而且[eta-reduced](https://en.wikipedia.org/wiki/Lambda_calculus#%CE%B7-reduction)(这使得它更加干燥)。哦,默认参数也可以被覆盖,但不应该被覆盖 - 一个潜在的陷阱。 (3认同)