Connecting slots and signals in PyQt4 in a loop

I am trying to build a calculator with PyQt4 and connect the "clicked ()" signals from the buttons not as expected. Im creating my buttons for numbers inside a for loop, where I try to connect them later.

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, they all print "9". Why is this and how can I fix it?

+7
source share
3 answers

This is true, as scoping, searching and closing names are defined in Python.

Python introduces new bindings in the namespace through assignment and function parameter lists. i therefore not defined in the lambda namespace, but in the __init__() namespace. The search for the name i in lambda, therefore, ends in the __init__() namespace, where i bound to 9 . This is called a "close".

You can get around this admittedly non-intuitive (but well-defined) semantics by passing i as a keyword argument with a default value. As said, the names in the parameter lists introduce new bindings in the local namespace, so the i inside lambda becomes independent of the i in .__init__() :

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

A more readable, less magical alternative is functools.partial :

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

I use the new signal and slot syntax here just for convenience, the old style syntax works the same.

+12
source

You create a closure. Closing really captures the variable, not the value of the variable. At the end, __init__ i is the last element of range(0, 10) , i.e. 9 . All lambdas that you created in this area belong to this i and only when they call do they get the value i at the time they are called (however, separate calls to __init__ create lambdas, referring to separate variables!).

There are two popular ways to avoid this:

  • Using the default parameter: lambda i=i: self._number(i) . This work, because the default parameters bind the value during function definition.
  • Defining helper function helper = lambda i: (lambda: self._number(i)) and using helper(i) in a loop. This works because the "external" i is evaluated at the time i is bound and, as mentioned earlier, the next closure created the next time helper called will refer to another variable.
+2
source

Use the Qt method, use QSignalMapper .

0
source

All Articles