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

Connecting slots and signals in PyQt4 in a loop

提问人:lukad 提问时间:1/2/2011 最后编辑:lukad 更新时间:7/1/2022 访问量:6250

问:

我尝试使用 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 信号槽

评论


答:

20赞 user355252 1/2/2011 #1

这就是在 Python 中定义范围、名称查找和闭包的方式。

Python 仅通过赋值和函数的参数列表在命名空间中引入新的绑定。 因此,实际上并没有在 的命名空间中定义,而是在 的命名空间中定义。因此,lambda 中的名称查找最终会出现在 的命名空间中,其中最终绑定到 。这称为“闭合”。ilambda__init__()i__init__()i9

您可以通过将具有默认值的关键字参数作为关键字参数传递来解决这些公认的不太直观(但定义明确)的语义。如前所述,参数列表中的名称在本地命名空间中引入了新的绑定,因此 then 内部独立于 in:iilambdai.__init__()

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

更新:clicked 有一个默认的选中参数,该参数将覆盖 i 的值,因此必须将其添加到关键字值之前的参数列表中。

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

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

为了方便起见,我在这里使用新式的信号和时隙语法,旧式语法的工作方式是一样的。

评论

0赞 lukad 1/2/2011
谢谢。我将使用 functools.partial 解决方案。
4赞 user395760 1/2/2011 #2

您正在创建闭包。闭包实际上捕获的是变量,而不是变量的值。在 的末尾,是 的最后一个元素,即 。您在此作用域中创建的所有 lambda 都引用了此值,并且仅在调用它们时,它们才会获得调用时的值(但是,对 create lambda 的单独调用引用了单独的变量!__init__irange(0, 10)9ii__init__

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

  1. 使用默认参数:.之所以这样做,是因为默认参数在函数定义时绑定了一个值。lambda i=i: self._number(i)
  2. 定义帮助程序函数并在循环中使用。这是有效的,因为“外部”是在绑定时计算的,并且 - 如前所述 - 在下一次调用中创建的下一个闭包将引用不同的变量。helper = lambda i: (lambda: self._number(i))helper(i)iihelper
0赞 ismail 1/2/2011 #3

使用方式,请改用 QSignalMapperQt

评论

1赞 1/2/2011
请不要。 是 C++ 98 的残余,它没有 lambda 或部分函数,并且这种解决方法是必要的邪恶。然而,在 Python 中,与 lambda 相比,它简直是多余的,而且非常臃肿。具有 或 的解决方案比 更短、更优雅。QSignalMapperQSignalMapperfunctools.partial()partial()lambdaQSignalMapper
0赞 ismail 1/2/2011
哦,好吧,这是关于选择,我会选择Qt / C++兼容性。QSignalMapper
1赞 1/4/2011
当然是关于选择,但我就是不理解你的选择,我无法遵循它。我之所以将 Python 用于 Qt 应用程序,正是因为它C++,如果我放弃 Python 的所有特殊功能来维护或达到C++兼容性,我也可以使用C++ ;)