13

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?

lukad
  • 17,287
  • 3
  • 36
  • 53

3 Answers3

20

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.

musicamante
  • 41,230
  • 6
  • 33
  • 58
4

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

Use the Qt way, use QSignalMapper instead.

ismail
  • 46,010
  • 9
  • 86
  • 95
  • 1
    Please don't. `QSignalMapper` is a remnant of C++ 98, which does not have lambdas or partial functions and where such workaround are a necessary evil. In Python however `QSignalMapper` is simply superfluous and really bloated compared to `functools.partial()` or lambda. Solutions with `partial()` or `lambda` are shorter and more elegant than `QSignalMapper`. –  Jan 02 '11 at 15:51
  • Oh well its about the choice, I would choose `QSignalMapper` for Qt/C++ compatibility. – ismail Jan 02 '11 at 15:59
  • 1
    Of course it's about the choice, but I simply don't understand your choice and I can't follow it. I'm using Python for Qt applications exactly because it is *not* C++, and if I'd abandon all of Python's special features to maintain or to reach C++ compatibility, I could just as well use C++ ;) –  Jan 03 '11 at 19:10