变量如何在 lambda 函数中工作?
How do variable work inside a lambda function?
我正在尝试使用 lambda 函数和 python 3.6 中的一些 PyQt5 QPushButton 将 9 个不同的按钮连接到一个处理程序。如果我使用 ints 单独分配它们,一切正常。但是,如果我尝试使用列表和循环,它们都会被分配给编号为 10 的按钮。我不明白为什么,因为我会认为我的分配是整数值,而我的变量超出了范围.显然,这里发生了一些我不明白的事情。谁能解释这段代码的行为?
self.buttonList = [ self.sq1Button,
self.sq2Button,
self.sq3Button,
self.sq4Button,
self.sq5Button,
self.sq6Button,
self.sq7Button,
self.sq8Button,
self.sq9Button]
buttonNumber = 1
for button in self.buttonList:
button.clicked.connect(lambda: self.squareButtonHandler(buttonNumber))
buttonNumber += 1
我曾经遇到过同样的问题,this 帮助了我。您基本上需要做的是将点击处理程序移动到一个单独的函数,并从循环内部使用 buttonNumber
调用该函数。这可能是由于闭包的工作方式 and/or 因为每次循环运行时它都需要一个新的 buttonNumber
。我仍然不明白确切的原因,所以如果有人知道,请comment/edit。
当python执行一个函数时,它会创建一个命名空间来保存局部变量。
中的 lambda
button.clicked.connect(lambda: self.squareButtonHandler(buttonNumber))
是一个内部函数,在外部作用域中包含对 buttonNumber
的引用。当您将该 lambda 传递给 button.clicked.connect
时,python 必须以某种方式记住该引用。它通过将外部范围的上下文添加到它创建并传递给 connect
的函数对象来实现这一点。您连接的所有按钮的函数对象都引用相同的外部上下文,这意味着当函数退出时,它们都会看到 buttonNumber
中的内容。
这是一个 运行 示例来说明您的问题
def buttonHandler(num):
print('button', num)
def try_lambda():
handlers = []
for num in range(5):
handlers.append(lambda: buttonHandler(num))
return handlers
print("test 1")
for handler in try_lambda():
handler()
产生
test 1
button 4
button 4
button 4
button 4
button 4
是的,这就是问题所在。让我们通过查看函数对象的闭包来查看我们创建的函数对象
print("test 2")
for handler in try_lambda():
handler()
print(handler, handler.__closure__)
显示
test 2
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9d08> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9d90> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9e18> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9ea0> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e349a048> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
有意思。我们得到了 4 个不同的函数对象(0x7f66e34a9d08 等),但是一个单元格在 0x7f66e49fb3a8 处保存了我们想要的变量。这就是为什么他们都看到相同的数字 - 他们都使用来自外部函数局部变量的相同保存单元格。
对于您的情况,partial
是更好的选择。它使用变量的 current 值创建一个函数,并按您想要的方式工作。
import functools
def try_partial():
handlers = []
for num in range(5):
handlers.append(functools.partial(buttonHandler, num))
return handlers
print("test 3")
for handler in try_partial():
handler()
产生
test 3
button 0
button 1
button 2
button 3
button 4
我正在尝试使用 lambda 函数和 python 3.6 中的一些 PyQt5 QPushButton 将 9 个不同的按钮连接到一个处理程序。如果我使用 ints 单独分配它们,一切正常。但是,如果我尝试使用列表和循环,它们都会被分配给编号为 10 的按钮。我不明白为什么,因为我会认为我的分配是整数值,而我的变量超出了范围.显然,这里发生了一些我不明白的事情。谁能解释这段代码的行为?
self.buttonList = [ self.sq1Button,
self.sq2Button,
self.sq3Button,
self.sq4Button,
self.sq5Button,
self.sq6Button,
self.sq7Button,
self.sq8Button,
self.sq9Button]
buttonNumber = 1
for button in self.buttonList:
button.clicked.connect(lambda: self.squareButtonHandler(buttonNumber))
buttonNumber += 1
我曾经遇到过同样的问题,this 帮助了我。您基本上需要做的是将点击处理程序移动到一个单独的函数,并从循环内部使用 buttonNumber
调用该函数。这可能是由于闭包的工作方式 and/or 因为每次循环运行时它都需要一个新的 buttonNumber
。我仍然不明白确切的原因,所以如果有人知道,请comment/edit。
当python执行一个函数时,它会创建一个命名空间来保存局部变量。
中的 lambdabutton.clicked.connect(lambda: self.squareButtonHandler(buttonNumber))
是一个内部函数,在外部作用域中包含对 buttonNumber
的引用。当您将该 lambda 传递给 button.clicked.connect
时,python 必须以某种方式记住该引用。它通过将外部范围的上下文添加到它创建并传递给 connect
的函数对象来实现这一点。您连接的所有按钮的函数对象都引用相同的外部上下文,这意味着当函数退出时,它们都会看到 buttonNumber
中的内容。
这是一个 运行 示例来说明您的问题
def buttonHandler(num):
print('button', num)
def try_lambda():
handlers = []
for num in range(5):
handlers.append(lambda: buttonHandler(num))
return handlers
print("test 1")
for handler in try_lambda():
handler()
产生
test 1
button 4
button 4
button 4
button 4
button 4
是的,这就是问题所在。让我们通过查看函数对象的闭包来查看我们创建的函数对象
print("test 2")
for handler in try_lambda():
handler()
print(handler, handler.__closure__)
显示
test 2
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9d08> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9d90> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9e18> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9ea0> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e349a048> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
有意思。我们得到了 4 个不同的函数对象(0x7f66e34a9d08 等),但是一个单元格在 0x7f66e49fb3a8 处保存了我们想要的变量。这就是为什么他们都看到相同的数字 - 他们都使用来自外部函数局部变量的相同保存单元格。
对于您的情况,partial
是更好的选择。它使用变量的 current 值创建一个函数,并按您想要的方式工作。
import functools
def try_partial():
handlers = []
for num in range(5):
handlers.append(functools.partial(buttonHandler, num))
return handlers
print("test 3")
for handler in try_partial():
handler()
产生
test 3
button 0
button 1
button 2
button 3
button 4