问题
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:
- Connect button
phase_scan_buttonclickedsignalto thescan_callbackslotand send the stringphaseas a parameter to theslot. The same foretalon,mirrorandgain.
What I'm getting:
- For some reason my functions is always passing the string
gainas 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()
回答1:
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.
回答2:
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
回答3:
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.
来源:https://stackoverflow.com/questions/46300229/connecting-multiples-signal-slot-in-a-for-loop-in-pyqt