为什么在此示例中需要 lambda 表达式? (Python)

Why is lambda expression necessary in this example? (Python)

我正在 Python 学习一点 Tkinter,创建交互式 windows。 window 我目前正在做的是,给定一个联系人字典及其各自的联系信息,为每个联系人创建一个按钮,按下该按钮时会显示他们的联系信息。

我的两个示例联系人名为 '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 表达式会阻止命令在单击按钮之前执行?它有什么功能用途?

就像您猜到的那样,当您使用 command = showinfo(name) 时,您要求 python 立即 调用 ,然后将结果分配给command 属性。

必须为 command 属性提供对函数的 引用lambda 是创建匿名函数和 return 引用的便捷方法,引用被分配给属性。在该匿名函数内,您可以调用您想要的任何其他函数,并且在执行匿名函数之前不会执行内部代码。

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

1另一种完成同样事情的方法是使用 functools.partial. Another method would be to write your own decorator

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

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 函数都需要 name 在创建 lambda 时的值。依靠闭包来保持 name 的值是行不通的,因为 name 的值在循环的每次迭代中都会发生变化。