在讨论 lambda/partial 和其他一些问题之间的差异之前,我将简要介绍针对您的代码示例的一些实用修复。
有两个主要问题。第一个是 for 循环中名称绑定的常见问题,如本问题中所述:循环中的 Lambda。第二个是具有默认信号参数的 PyQt 特定问题,如本问题中所述:无法从 Python 循环中创建的按钮发送信号。鉴于此,您的示例可以通过连接信号来修复,如下所示:
bh.clicked.connect(lambda checked, n=n: self.printtext(n+1))
所以这会缓存当前值n
在默认参数中,还添加了一个checked
停止争论n
被信号发出的参数覆盖。
该示例还可以受益于以更惯用的方式重写,从而消除令人讨厌的 eval hack:
layout = QtWidgets.QVBoxLayout(self)
for n in range(1, 4):
button = QtWidgets.QPushButton(f'button {n}')
button.clicked.connect(lambda checked, n=n: self.printtext(n))
layout.addWidget(button)
setattr(self, f'button{n}', button)
或使用Q按钮组:
self.buttonGroup = QtWidgets.QButtonGroup(self)
layout = QtWidgets.QVBoxLayout(self)
for n in range(1, 4):
button = QtWidgets.QPushButton(f'button {n}')
layout.addWidget(button)
self.buttonGroup.addButton(button, n)
setattr(self, f'button{n}', button)
self.buttonGroup.buttonClicked[int].connect(self.printtext)
(请注意,也可以通过选择按钮,然后在上下文菜单中选择“分配到按钮组”,在 Qt Designer 中创建按钮组)。
至于lambda和partial的区别问题:主要是前者隐式创建了一个closure覆盖局部变量,而后者显式存储内部传递给它的参数。
Python 中闭包的常见“问题”是循环内定义的函数不会捕获所包含变量的当前值。因此,当稍后调用该函数时,它只能访问最后看到的值。有一个最近在 python-ideas 邮件列表上讨论了更改此行为,但似乎没有得出任何确定的结论。不过,Python 的未来版本可能会消除这个小问题。完全避免了这个问题partial
函数,因为它创建了一个可调用对象它将传递给它的参数存储为只读属性。因此,在 a 中使用默认参数的解决方法lambda
显式存储变量实际上是使用相同的方法。
这里还有一点值得讨论:使用默认参数连接信号时 PyQt 和 PySide 之间的差异。看来 PySide 将所有插槽视为装饰有插槽装饰器;而 PyQt 则以不同的方式对待未修饰的槽。以下是差异的说明:
def __init__(self):
...
self.button_1.clicked.connect(self.slot1)
self.button_2.clicked.connect(self.slot2)
self.button_3.clicked.connect(self.slot3)
def slot1(self, n=1): print(f'slot1: {n=!r}')
def slot2(self, *args, n=1): print(f'slot2: {args=!r}, {n=!r}')
def slot3(self, x, n=1): print(f'slot3: {x=!r}, {n!r}')
依次单击每个按钮后,将产生以下输出:
PyQt5 output:
slot1: n=False
slot2: args=(False,), n=1
slot3: x=False, n=1
PySide2 output:
slot1: n=False
slot2: args=(), n=1
TypeError: slot3() missing 1 required positional argument: 'x'
如果插槽装饰有@QtCore.pyqtSlot()
,PyQt5 的输出与上面显示的 PySide2 输出匹配。因此,如果您需要一个对 PyQt 和 PySide 都具有相同作用的 lambda(或任何其他未修饰槽)解决方案,您应该使用:
bh.clicked.connect(lambda *args, n=n: self.printtext(n))
PS:
另请参阅我对以下问题的有关 PySide2/6 进一步差异的回答:
- PySide-6.6:单击的信号将额外的布尔参数发送到插槽