Im trying to build a calculator with PyQt4 and connecting the ‘clicked()’ signals from the buttons doesn’t work as expected. Im creating my buttons for the numbers inside a for loop where i try to connect them afterwards.
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’. Why is that so and how can i fix this?
Advertisement
Answer
This is just, how scoping, name lookup and closures are defined in Python.
Python only introduces new bindings in namespace through assignment and through parameter lists of functions. i
is therefore not actually defined in the namespace of the lambda
, but in the namespace of __init__()
. The name lookup for i
in the lambda consequently ends up in the namespace of __init__()
, where i
is eventually bound to 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. 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__()
:
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.
A more readable, less magic alternative is 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.