循环连接PyQt4中的槽和信号

问题描述 投票:0回答:3

我尝试使用 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”。 为什么会这样?我该如何解决这个问题?

python pyqt4 signals-slots
3个回答
20
投票

这就是 Python 中如何定义范围、名称查找和闭包。

Python 仅通过赋值和函数参数列表在命名空间中引入新的绑定。因此,

i
实际上并不是在
lambda
的命名空间中定义的,而是在
__init__()
的命名空间中定义的。因此,在 lambda 中对
i
的名称查找最终会在
__init__()
的命名空间中结束,其中
i
最终绑定到
9
。这就是所谓的“关闭”。

您可以通过将

i
作为具有默认值的关键字参数传递来解决这些确实不直观(但定义明确)的语义。如前所述,参数列表中的名称在本地命名空间中引入了新的绑定,因此
i
中的
lambda
然后变得独立于
i
中的
.__init__()
:

self._numberButtons[i].clicked.connect(lambda checked, i=i: self._number(i))

更新:

clicked
有一个默认的
checked
参数,它将覆盖
i
的值,因此它必须添加到关键字值之前的参数列表中。

一个更具可读性、不那么神奇的替代方案是

functools.partial

self._numberButtons[i].clicked.connect(partial(self._number, i))

我在这里使用新型信号和槽语法只是为了方便,旧式语法的工作原理是一样的。


4
投票

你正在创造封闭。闭包真正捕获的是变量,而不是变量的值。在

__init__
的末尾,
i
range(0, 10)
的最后一个元素,即
9
。您在此范围内创建的所有 lambda 都引用此
i
,并且仅当调用它们时,它们才会在调用时获取
i
的值(但是,单独调用
__init__
创建引用单独的 lambda变量!)。

有两种流行的方法可以避免这种情况:

  1. 使用默认参数:
    lambda i=i: self._number(i)
    。这是因为默认参数在函数定义时绑定了一个值。
  2. 定义辅助函数
    helper = lambda i: (lambda: self._number(i))
    并在循环中使用
    helper(i)
    。这是有效的,因为“外部”
    i
    是在绑定
    i
    时评估的,并且 - 如前所述 - 在下一次调用
    helper
    中创建的下一个闭包将引用不同的变量。

0
投票

使用

Qt
方式,请使用
QSignalMapper
代替。

© www.soinside.com 2019 - 2024. All rights reserved.