【问题标题】:Why is lambda expression necessary in this example? (Python)为什么在这个例子中需要 lambda 表达式? (Python)
【发布时间】:2015-06-30 12:52:35
【问题描述】:

我正在学习一点 Python 中的 Tkinter,创建交互式窗口。我目前正在制作的窗口是一个窗口,给定联系人及其各自的联系信息,为每个联系人创建一个按钮,按下时显示他们的联系信息。

我的两个示例联系人名为“Marvin”和“Minsky”,他们的信息存储在名为 book 的字典中:

import Tkinter as tkinter

# 'Phonebook' with contact info for two people.
book = {"Marvin": {"Mobile": "1234567890", "Email": "marvin@gmail.com"}, 
"Minsky": {"Mobile": "9087865342", "Email": "minsky@yahoo.com"}} 

window = tkinter.Tk() # Make window object

def showinfo(name):
  # Displays info for person whose button was clicked

  # Displays info by configuring labels named 'mobile' and 'email', 
  # using values in 'book'
  mobile.configure(text = book[name]["Mobile"])
  email.configure(text = book[name]["Email"])

这是重要的部分。我第一次尝试使用 lambda 表达式,而不是简单地使用 command = showinfo(name) 调用 showinfo() 的按钮:

for name in sorted(phonedict.book.keys()): 
  # Create button for each alphabetically sorted name
  btn = tkinter.Button(text = name, command = lambda arg = name: showinfo(arg))
  btn.pack()

剩下的代码只是showinfo()修改的标签:

# Window section where contact info is displayed via labels
mobile_lbl = tkinter.Label(text = "Mobile:")
mobile_lbl.pack()
mobile = tkinter.Label(text = "")
mobile.pack()
email_lbl = tkinter.Label(text = "Email:")
email_lbl.pack()
email = tkinter.Label(text = "")
email.pack()

# Display window
window.mainloop()

运行时,该程序完全按照我的意愿执行,在单击每个按钮时正确修改标签。

如果使用 command = showinfo(name) 代替 lambda,但是,NameError: global name 'mobile' is not defined 会被抛出,因为(我认为)它会尝试在创建按钮时执行按钮的命令按下

为什么在按钮的命令中使用 lambda 表达式会阻止命令在单击按钮之前执行?它服务于什么功能目的?

【问题讨论】:

标签: python lambda tkinter tk


【解决方案1】:

如您所料,当您使用command = showinfo(name) 时,您要求python立即 调用showinfo(name),然后将其结果分配给command 属性。

command 属性必须被赋予一个函数的引用lambda 是创建匿名函数并返回分配给属性的引用的便捷方式。在该匿名函数中,您可以调用任何您想要的其他函数,并且在执行匿名函数之前不会执行该内部代码。

lambda 的功能目的是创建一个临时的、未命名的函数,该函数可以传递给其他函数或存储为属性。这是一种方便的方法(但不是唯一的方法1)围绕需要参数的回调创建包装器。

1完成同样事情的另一种方法是使用functools.partial。另一种方法是编写自己的decorator

【讨论】:

  • +1 用于 lambda 的简明解释。您能否详细说明答案中的“但不是唯一的方式”评论?只是一个好奇的菜鸟,想学习一切!谢谢!
  • @ChristopherPearson:我在回答中添加了 functools.partial 和装饰器的简短提及。
【解决方案2】:

回调只是一个传递给其他函数的函数,以便这些函数可以调用它。 showinfo(name) 不是回调,因为在构造 Button 之前立即调用该函数,并且 showinfo 的返回值似乎是 None (如果函数没有返回任何内容,则默认情况下返回 None在 Python 中)。

showinfo 本身,可能是一个回调,因为它是一个函数,但问题是它需要一个位置参数。在 Python 中,需要位置参数:

def f1(callback): callback()
def f2(arg1): pass

f1(f2) # TypeError: f2() takes exactly 1 argument (0 given)

您的代码解决此问题的方法是使用 lambda 表达式,该表达式采用我们动态定义为名称的默认参数。所以我们要说的是,这里有一个函数,它有一个关键字参数arg,默认设置为name,当调用这个函数时,使用该默认参数调用showinfo:

btn = tkinter.Button(text = name, command=lambda arg=name: showinfo(arg))

但是为什么我们需要使用默认参数呢?这不是一种复杂的做事方式吗? 是的,是的。你可以简单地这样做:

btn = tkinter.Button(text = name, command=lambda: showinfo(name))

这是一个不带参数的 lambda,与您正在执行的操作完全等效。这是因为 showinfoname 是您正在创建的 lambda 闭包的一部分,因此即使在 lambda 函数已传递给另一个函数之后,lambda 本身也可以访问它们。

是的,但这是必要的,因为您传入的每个 lambda 函数都需要名称的值在创建 lambda 时。依靠闭包来保持name 的值是行不通的,因为name 的值在循环的每次迭代中都会发生变化。

【讨论】:

  • 谢谢!我刚刚发现的最后一个建议是省略arg=name 而只使用showinfo(name) 的一个问题是,这将导致所有回调只引用变量而不是附加变量的当前值到每个循环迭代的回调。结果是,如果没有arg=name,循环中创建的每个按钮将只使用单击按钮时name当前值
  • @ZachW 注意!我已经对这个答案进行了编辑。好电话,双关语。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-06
  • 2020-03-26
  • 1970-01-01
  • 2018-04-29
  • 2014-12-23
  • 2021-05-17
相关资源
最近更新 更多