在循环中连接PyQt4中的插槽和信号

时间:2011-01-02 14:47:47

标签: python pyqt4 signals-slots

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

3 个答案:

答案 0 :(得分:14)

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

Python仅通过赋值和函数参数列表在命名空间中引入新的绑定。因此,i实际上并未在lambda的命名空间中定义,而是在__init__()的命名空间中定义。因此,lambda中i的名称查找最终位于__init__()的命名空间中,其中i最终绑定到9。这被称为“封闭”。

通过将i作为具有默认值的关键字参数传递,您可以解决这些不可靠的直观(但定义良好)语义。如上所述,参数列表中的名称在本地名称空间中引入了新的绑定,因此i中的lambda变得独立于i中的.__init__()

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

functools.partial是一个更具可读性,更少魔力的替代方案:

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

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

答案 1 :(得分:3)

您正在创建闭包。闭包实际上捕获变量,而不是变量的值。在__init__结束时,irange(0, 10)的最后一个元素,即9。您在此范围中创建的所有lambda都引用此i,并且只有在调用它们时,它们才会在调用它们时获得i的值(但是,__init__的单独调用创建lambdas引用单独的变量!)。

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

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

答案 2 :(得分:0)

使用Qt方式,改为使用QSignalMapper