在for循环中使用lambda i=i: foo(i) 无效
首先阅读这个。它讲的是 lambda x=x: foo(x)
在 for
循环中如何捕捉到 x 的值。
这里有一个窗口,里面有一个标签和两个按钮,这些都是在 for
循环中生成的。当点击按钮时,按钮的名字会显示在标签上。
如果我们用普通的 lambda: label.setText("button -- " + str(i))
,那么无论按哪个按钮,结果都会是循环中最后的 i
值:
这是正确的。
当我们改成 lambda i=i: label.setText("button -- " + str(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 个回答
与其通过默认参数来绑定,不如使用 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
的布尔参数覆盖。看到函数多了一个位置参数,这就可以提示我们去查看文档了。
信号“clicked”会把一个布尔值(真或假)传递给你连接的lambda槽函数。
文档链接
你想要实现的功能,其实用下面的方式会更好:
btn.clicked.connect( lambda clicked, i=i : label.setText("button " + str(i)) )
我现在没有安装PyQt4来测试,但我觉得很明显,当你的lambda回调被调用时,它会接收到一个参数。i
的值就会变成这个参数的值,而不是默认值。试试看这个,告诉我是否有效(或者至少输出有变化):
btn.clicked.connect( lambda throw_away=0, i=i: label.setText("button " + str(i)) )