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.
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?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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 i=i: self._number(i))

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.

Method 2

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), i.e. 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!).

There are two popular ways to avoid this:

  1. Using a default parameter: lambda i=i: self._number(i). This work because default parameters bind a value at function definition time.
  2. Defining a helper function helper = lambda i: (lambda: self._number(i)) and use helper(i) in the loop. 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.

Method 3

Use the Qt way, use QSignalMapper instead.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x