【问题标题】:How do variable work inside a lambda function?变量如何在 lambda 函数中工作?
【发布时间】:2017-04-15 12:38:20
【问题描述】:

我正在尝试使用 lambda 函数和 python 3.6 中的一些 PyQt5 QPushButtons 将 9 个不同的按钮连接到一个处理程序。如果我使用整数单独分配它们,一切正常。但是,如果我尝试使用列表和循环,它们都被分配给按钮编号 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

【问题讨论】:

  • stackoverflow.com/questions/141642/… 如果你看这个问题,典型的 python 闭包似乎是只读的。闭包是一个 lambda,它从其范围之外引用一个变量。
  • 你可以认为它类似于一个接受参数的对象,但它没有显式构造函数,而是隐含的 lambda。
  • @Novaterata - 有趣,但它们不一定是只读的。如果其他一些内部函数使用nonlocal buttonNumber,它可以为每个人更改值。
  • 是的,这似乎是该语言的新增功能,允许读/写闭包

标签: python lambda pyqt5 python-3.6


【解决方案1】:

当 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

【讨论】:

  • 不带partial 的选项是使用handlers.append(lambda num=num: buttonHandler(num))num 设为本地。
  • 我以前用过一次。一个朋友给了我代码,但我认为我并没有完全理解它。现在感谢tdelaney,我相信我做到了!谢谢!但是,当我尝试通过创建局部变量来保留 lambda 时,它只有在我使用 (lambda 检查,num=num: buttonHandler(num)) 时才有效。我从给定的代码中复制了这个,但我不知道“检查”位在做什么......
【解决方案2】:

我曾经遇到过同样的问题,this 帮助了我。您基本上需要做的是将点击处理程序移动到一个单独的函数,并从循环内部使用buttonNumber 调用该函数。这可能是由于闭包的工作方式和/或因为每次循环运行时它都需要一个新的buttonNumber。我仍然不明白确切的原因,所以如果有人这样做,请评论/编辑。

【讨论】:

    猜你喜欢
    • 2012-01-13
    • 1970-01-01
    • 1970-01-01
    • 2018-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-23
    • 2021-07-22
    相关资源
    最近更新 更多