When python executes a function, it creates a namespace to hold local variables. The lambda in
button.clicked.connect(lambda: self.squareButtonHandler(buttonNumber))
is an inner function that contains a reference to buttonNumber
in the outer scope. When you pass that lambda to button.clicked.connect
, python has to remember that reference somehow. It does that by adding the context of the outer scope to the function object that it creates and passes to connect
. The function objects for all of the buttons you connected referene the same outer context and that means they will all see whatever is in buttonNumber
when the function exits.
Here is a running example showing your problem
def buttonHandler(num):
print('button', num)
def try_lambda():
handlers = []
for num in range(5):
handlers.append(lambda: buttonHandler(num))
return handlers
print("test 1")
for handler in try_lambda():
handler()
It produces
test 1
button 4
button 4
button 4
button 4
button 4
Yep, that's the problem. Lets take a look at the function objects we created by looking at the function object's closure
print("test 2")
for handler in try_lambda():
handler()
print(handler, handler.__closure__)
It shows
test 2
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9d08> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9d90> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9e18> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e34a9ea0> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
button 4
<function try_lambda.<locals>.<lambda> at 0x7f66e349a048> (<cell at 0x7f66e49fb3a8: int object at 0xa68aa0>,)
Interesting. We got 4 different function objects (0x7f66e34a9d08, etc...) but a single cell holding the variable we want at 0x7f66e49fb3a8. That's why they all see the same number - they all use the same saved cell from the outer function's local variables.
In your case, partial
is a better option. It creates a function using a variable's current value and works likes you want.
import functools
def try_partial():
handlers = []
for num in range(5):
handlers.append(functools.partial(buttonHandler, num))
return handlers
print("test 3")
for handler in try_partial():
handler()
It produces
test 3
button 0
button 1
button 2
button 3
button 4