在循环中连接 PyQt4 中的插槽和信号
Posted
技术标签:
【中文标题】在循环中连接 PyQt4 中的插槽和信号【英文标题】:Connecting slots and signals in PyQt4 in a loop 【发布时间】:2019-07-25 08:29:55 【问题描述】:我试图用 PyQt4 构建一个计算器并连接来自按钮的“clicked()”信号没有按预期工作。 我在 for 循环中为数字创建按钮,之后我尝试将它们连接起来。
def __init__(self):
for i in range(0,10):
self._numberButtons += [QPushButton(str(i), self)]
self.connect(self._numberButtons[i], SIGNAL('clicked()'), lambda : self._number(i))
def _number(self, x):
print(x)
当我点击按钮时,所有按钮都会打印出“9”。 为什么会这样,我该如何解决?
【问题讨论】:
【参考方案1】:这只是在 Python 中定义范围、名称查找和闭包的方式。
Python 仅通过赋值和函数的参数列表在命名空间中引入新的绑定。因此i
实际上并未定义在lambda
的命名空间中,而是在__init__()
的命名空间中。因此,在 lambda 中查找 i
的名称最终在 __init__()
的命名空间中,其中 i
最终绑定到 9
。这称为“闭包”。
您可以通过将i
作为具有默认值的关键字参数传递来解决这些公认的不是很直观(但定义明确)的语义。如前所述,参数列表中的名称在本地命名空间中引入了新的绑定,因此lambda
中的i
然后独立于.__init__()
中的i
:
self._numberButtons[i].clicked.connect(lambda i=i: self._number(i))
functools.partial
是一个更易读、更少魔法的替代方案:
self._numberButtons[i].clicked.connect(partial(self._number, i))
我在这里使用新风格的信号和槽语法只是为了方便,旧风格的语法是一样的。
【讨论】:
谢谢。我将使用 functools.partial 解决方案。【参考方案2】:您正在创建闭包。闭包确实捕获了一个变量,而不是变量的值。在__init__
的末尾,i
是range(0, 10)
的最后一个元素,即9
。您在此范围内创建的所有 lambdas 都引用此 i
并且仅当它们被调用时,它们才会在它们被调用时获得 i
的值(但是,__init__
的单独调用会创建引用单独的 lambdas变量!)。
有两种流行的方法可以避免这种情况:
-
使用默认参数:
lambda i=i: self._number(i)
。这是因为默认参数在函数定义时绑定了一个值。
定义辅助函数helper = lambda i: (lambda: self._number(i))
并在循环中使用helper(i)
。这是因为“外部”i
是在绑定 i
时进行评估的,并且 - 如前所述 - 在下一次调用 helper
时创建的下一个闭包将引用不同的变量。
【讨论】:
【参考方案3】:使用Qt
方式,改用QSignalMapper
。
【讨论】:
请不要。QSignalMapper
是 C++ 98 的残余,它没有 lambda 或部分函数,并且这种变通方法是必要的。然而,在 Python 中,QSignalMapper
与functools.partial()
或 lambda 相比是多余的,而且确实臃肿。使用partial()
或lambda
的解决方案比QSignalMapper
更短、更优雅。
哦,关于选择,我会选择 QSignalMapper
以实现 Qt/C++ 兼容性。
当然是关于选择的问题,但我根本不明白你的选择,我也无法遵循。我将 Python 用于 Qt 应用程序正是因为它不是 C++,如果我放弃 Python 的所有特殊功能来维护或达到 C++ 兼容性,我也可以使用 C++ ;) 以上是关于在循环中连接 PyQt4 中的插槽和信号的主要内容,如果未能解决你的问题,请参考以下文章
Qt 4.8:来自不同线程的两个信号和一个插槽之间的连接行为
如何使用 Qt Creator 将按钮单击信号(“触发”信号)与工具栏中的用户按钮的动作/插槽功能连接起来?