问题
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?
回答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.
回答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:
- Using a default parameter:
lambda i=i: self._number(i). This work because default parameters bind a value at function definition time. - Defining a helper function
helper = lambda i: (lambda: self._number(i))and usehelper(i)in the loop. This works because the "outer"iis evaluated at the timeiis bound, and - as mentioned before - the next closure created in the next invokation ofhelperwill refer to a different variable.
回答3:
Use the Qt way, use QSignalMapper instead.
来源:https://stackoverflow.com/questions/4578861/connecting-slots-and-signals-in-pyqt4-in-a-loop