在for循环中使用lambda i=i: foo(i) 无效

5 投票
3 回答
755 浏览
提问于 2025-04-16 18:30

首先阅读这个。它讲的是 lambda x=x: foo(x)for 循环中如何捕捉到 x 的值。

这里有一个窗口,里面有一个标签和两个按钮,这些都是在 for 循环中生成的。当点击按钮时,按钮的名字会显示在标签上。

如果我们用普通的 lambda: label.setText("button -- " + str(i)),那么无论按哪个按钮,结果都会是循环中最后的 i 值:
lambda:foo(i)
这是正确的。

当我们改成 lambda i=i: label.setText("button -- " + str(i))(代码片段),并期待这样就能正常工作时,结果却是:
lambda i=i:foo(i)]
结果是错误的!

这个 False 是从哪里来的呢?

import sys
from PyQt4.QtGui import *

class MainWindow(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        vbox = QVBoxLayout(self)

        # label for action
        label = QLabel('')
        vbox.addWidget(label)

        # adding buttons
        for i in range (1, 3):
            btn = QPushButton(str(i))
            btn.clicked.connect( lambda i=i: label.setText("button " + str(i)) )
            vbox.addWidget(btn)

app = QApplication(sys.argv)
myapp = MainWindow()
myapp.show()
sys.exit(app.exec_())

为什么这个解决方案没有按预期工作?这个 false 代表什么?

我知道可以像第一个链接那样创建 foo_factory,但问题是 lambda i=i: foo(i) 到底哪里出了问题?

3 个回答

-1

与其通过默认参数来绑定,不如使用 functools.partial 这种方式,这样更容易找到问题所在。

正确的代码(如果我理解其他回答没错的话;我没有使用过 PyQT)应该是这样的:

from functools import partial

# and then:
set_to_i = partial(label.setText, f"button {i}")
btn.clicked.connect(lambda clicked: set_to_i())

这样,我们就是用一个专门为这个任务设计的工具来绑定值,而不是利用一些通常被认为是“陷阱”的方式。值得注意的是,如果我们一开始就采用这种方法,但忽略了 clicked 参数(比如直接写 btn.clicked.connect(set_to_i)),我们会遇到 TypeError 错误,而不是默认绑定的 i 被应该是 clicked 的布尔参数覆盖。看到函数多了一个位置参数,这就可以提示我们去查看文档了。

3

信号“clicked”会把一个布尔值(真或假)传递给你连接的lambda槽函数。
文档链接

你想要实现的功能,其实用下面的方式会更好:

btn.clicked.connect( lambda clicked, i=i : label.setText("button " + str(i)) )
4

我现在没有安装PyQt4来测试,但我觉得很明显,当你的lambda回调被调用时,它会接收到一个参数。i的值就会变成这个参数的值,而不是默认值。试试看这个,告诉我是否有效(或者至少输出有变化):

btn.clicked.connect( lambda throw_away=0, i=i: label.setText("button " + str(i)) )

撰写回答