简体   繁体   English

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

[英]Connecting slots and signals in PyQt4 in a loop

Im trying to build a calculator with PyQt4 and connecting the 'clicked()' signals from the buttons doesn't work as expected.我试图用 PyQt4 构建一个计算器并连接来自按钮的“clicked()”信号没有按预期工作。 Im creating my buttons for the numbers inside a for loop where i try to connect them afterwards.我为 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)

When I click on the buttons all of them print out '9'.当我单击按钮时,所有按钮都会打印出“9”。 Why is that so and how can i fix this?为什么会这样,我该如何解决?

This is just, how scoping, name lookup and closures are defined in Python.这就是 Python 中定义范围、名称查找和闭包的方式。

Python only introduces new bindings in namespace through assignment and through parameter lists of functions. Python 仅通过赋值和函数的参数列表在命名空间中引入新的绑定。 i is therefore not actually defined in the namespace of the lambda , but in the namespace of __init__() .因此, i实际上并未在lambda的命名空间中定义,而是在__init__()的命名空间中定义。 The name lookup for i in the lambda consequently ends up in the namespace of __init__() , where i is eventually bound to 9 .因此,在 lambda 中查找i的名称最终在__init__()的命名空间中,其中i最终绑定到9 This is called "closure".这被称为“关闭”。

You can work around these admittedly not really intuitive (but well-defined) semantics by passing i as a keyword argument with default value.您可以通过将i作为具有默认值的关键字参数传递来解决这些公认的不是很直观(但定义明确)的语义。 As said, names in parameter lists introduce new bindings in the local namespace, so i inside the lambda then becomes independent from i in .__init__() :如前所述,参数列表中的名称在本地命名空间中引入了新的绑定,因此lambda中的i然后独立于.__init__()中的i

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

UPDATE: clicked has a default checked argument that would override the value of i , so it must be added to the argument list before the keyword value.更新: clicked有一个默认的checked参数,它会覆盖i的值,所以它必须在关键字值之前添加到参数列表中。

A more readable, less magic alternative is functools.partial : functools.partial是一个更易读、更少魔法的替代方案:

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

I'm using new-style signal and slot syntax here simply for convenience, old style syntax works just the same.我在这里使用新式的信号和槽语法只是为了方便,旧式的语法是一样的。

You are creating closures.您正在创建闭包。 Closures really capture a variable, not the value of a variable.闭包确实捕获了一个变量,而不是变量的值。 At the end of __init__ , i is the last element of range(0, 10) , ie 9 .__init__的末尾, irange(0, 10)的最后一个元素,即9 All the lambdas you created in this scope refer to this i and only when they are invoked, they get the value of i at the time they are at invoked (however, seperate invocations of __init__ create lambdas referring to seperate variables.)您在此 scope 中创建的所有 lambdas 都引用此i并且仅当它们被调用时,它们才会在它们被调用时获得i的值(但是, __init__的单独调用会创建引用单独变量的 lambdas。)

There are two popular ways to avoid this:有两种流行的方法可以避免这种情况:

  1. Using a default parameter: lambda i=i: self._number(i) .使用默认参数: lambda i=i: self._number(i) This work because default parameters bind a value at function definition time.这是因为默认参数在 function 定义时间绑定了一个值。
  2. Defining a helper function helper = lambda i: (lambda: self._number(i)) and use helper(i) in the loop.定义一个助手 function helper = lambda i: (lambda: self._number(i))并在循环中使用helper(i) This works because the "outer" i is evaluated at the time i is bound, and - as mentioned before - the next closure created in the next invokation of helper will refer to a different variable.这是因为“外部” ii被绑定时被评估,并且 - 如前所述 - 在下一次调用helper时创建的下一个闭包将引用不同的变量。

Use the Qt way, use QSignalMapper instead.使用Qt方式,改用QSignalMapper

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM