Reputation: 641
I'm connecting multiple signal/slots using a for loop
in PyQt. The code is bellow:
# Connect Scan Callbacks
for button in ['phase', 'etalon', 'mirror', 'gain']:
getattr(self.ui, '{}_scan_button' .format(button)).clicked.connect(
lambda: self.scan_callback(button))
What I expect:
phase_scan_button
clicked signal
to the scan_callback
slot
and send the string phase
as a parameter to the slot
. The same for etalon
, mirror
and gain
.What I'm getting:
gain
as parameter for all the buttons. Not sure if I'm being stupid (likely) or it is a bug.For reference, the slot
method:
def scan_callback(self, scan):
print(scan) # Here I always get 'gain'
if self.scanner.isWorking:
self.scanner.isWorking = False
self.scan_thread.terminate()
self.scan_thread.wait()
else:
self.scanner.isWorking = True
self.scan_thread.start()
getattr(self.ui, '{}_scan_button' .format(
scan)).setText('Stop Scan')
getattr(self, '_signal{}Scan' .format(scan)).emit()
Upvotes: 3
Views: 4468
Reputation: 11
As noted here : Connecting slots and signals in PyQt4 in a loop
Using functools.partial
is a nice workaround for this problem.
Have been struggling with same problem as OP for a day.
Upvotes: 1
Reputation: 2801
My preferred way of iterating over several widgets in pyqt is storing them as objects in lists.
myButtons = [self.ui.phase_scan_button, self.ui.etalon_scan_button,
self.ui.mirror_scan_button, self.ui.gain_scan_button]
for button in myButtons:
button.clicked.connect(lambda _, b=button: self.scan_callback(scan=b))
If you need the strings "phase", "etalon", "mirror" and "gain" separately, you can either store them in another list, or create a dictionary like
myButtons_dict = {"phase": self.ui.phase_scan_button,
"etalon": self.ui.etalon_scan_button,
"mirror": self.ui.mirror_scan_button,
"gain": self.ui.gain_scan_button}
for button in myButtons_dict:
myButtons_dict[button].clicked.connect(lambda: _, b=button self.scan_callback(scan=b))
Note, how I use the lambda expression with solid variables that are then passed into the function self.scan_callback
. This way, the value of button
is stored for good.
Upvotes: 6
Reputation: 19123
Your lambdas do not store the value of button
when it is defined. The code describing the lambda function is parsed and compiled but not executed until you actually call the lambda.
Whenever any of the buttons is clicked, the current value of variable button
is used. At the end of the loop, button
contains "gain"
and this causes the behaviour you see.
Try this:
funcs = []
for button in ['phase', 'etalon', 'mirror', 'gain']:
funcs.append( lambda : print(button))
for fn in funcs:
fn()
The output is:
gain
gain
gain
gain
Extending the example, as a proof that the lambdas don't store the value of button
note that if button
stops existing, you'll have an error:
del button
for fn in funcs:
fn()
which has output
funcs.append( lambda : print(button))
NameError: name 'button' is not defined
Upvotes: 3