18. More Signals & Slots
18.1 A Common Pitfall
We have already seen that a Python lambda can be used to pass additional arguments to a slot, however, there is a pitfall that makes lambdas a bit tricky. The value of a variable used in a lambda is looked up at the time it is called1. Let’s say you have five lambda functions and you want to pass them a loop index as the parameter:
1 functions = []
2
3 for i in range(5):
4 functions.append(lambda: print(i))
5
6 print("Calling the functions after the loop:")
7 for func in functions:
8 func()
The output is:
1 Calling the functions after the loop:
2 4
3 4
4 4
5 4
6 4
When the functions are executed (in the second loop) the i variable value is 4 so all four print 4 instead of 0, 1, 2, 3 and 4 as one would expect. You can change this by assigning the current index to a lambda argument:
1 functions = []
2
3 for i in range(5):
4 functions.append(lambda x=i: print(x))
5
6 print("Calling the fixed functions:")
7 for func in functions:
8 func()
Now, the output is:
1 Calling the fixed functions:
2 0
3 1
4 2
5 3
6 4
![]() |
You want to log each |
Create the checkbox.
Use a loop to connect its
checkStateChanged()signal to the slots. In each loop iteration, capture the index and checkboxcheckState()current valuesLog the checkbox state changes.
Now, if you check the checkbox, the output is:
1 Logging to file no: 0
2 State: CheckState.Checked
3 Logging to file no: 1
4 State: CheckState.Checked
5 Logging to file no: 2
6 State: CheckState.Checked
7 Logging to file no: 3
8 State: CheckState.Checked
9 Logging to file no: 4
10 State: CheckState.Checked
18.2 Custom Signals
Most of the Qt classes provide a set of predefined signals. However, when creating your own QObject inherited class, you may want to provide custom signals to accompany it.
![]() |
Suppose we want to create a custom button class that that keeps track of the number of times the user has clicked on it. We also want the class to be able to notify other classes when the counter changes. |
PySide6 provides the means to do this in a pythonic way:
Import the
Signalclass from thePySide6.QtCorenamespace.Signalprovides theconnect(),disconnect()andemit()methods.Create a
QObjectinherited class and add a signal to it. We inherit the class fromQPushButtonand add a signal namedcounterChanged()to it. The signal is declared as a class-level variable of the class and takes a list of Python types as argument.counterChanged()takes a singleintargument. Each time the button is clicked we increment the counter and emit the signal, passing it theself.countervariable.In the main window use the custom signal the same way as the predefined Qt signals. We create an instance of the
CounterButton, create a slot namedon_counter_changed(), and connect the button’scounterChangedsignal with it. Now, each time the button is clicked, the counter is incremented and thecounterChanged()signal is emitted:
1 Counter: 1
2 Button clicked
3 Counter: 2
4 Button clicked
5 Counter: 3
6 Button clicked
18.3 Signal Blocking
At times, you may want to prevent signal emission, for instance during class initialization, or when making programmatic changes to a widget’s values that would trigger a signal.
![]() |
Your task is to allow the user to temporarily block a button’s |
To temporarily block a signal, use the QObject.blockSignals() passing it a boolean value. If the value is True all signals emitted by the object are blocked. If the value is False, signals are not blocked.
In the example we have a button’s clicked() signal connected to a slot named on_button_clicked(). We use a QCheckBox() to block/unblock the button’s signals:
1 if state == Qt.CheckState.Checked:
2 self.button.blockSignals(True)
3 print('Signals blocked!')
4 else:
5 self.button.blockSignals(False)
6 print('Signals unblocked!')
When the checkbox is checked the button’s signals are blocked:
1 Button clicked, checked: False
2 Signals blocked!
3 Signals unblocked!
4 Button clicked, checked: False
18.4 Connection Objects
The Signal.connect() method has a return value of type QMetaObject.Connection. It is a handle to the signal-slot connection the Signal.connection() call established.
![]() |
You need to enable the user to connect or disconnect a button’s signals. |
To use a connection object in your application:
Store a reference to the
Connectionobject thatSignal.connect()returned. We store the reference in the instance variable namedconn.Use the reference to disconnect the signal from the slot. Aside from
connect()PuSide6Signalobject also have a method nameddisconnect()that we use to disconnect the signal from the slot.
The output is:
1 <class 'PySide6.QtCore.QMetaObject.Connection'>
2 Connection is valid
3 <class 'PySide6.QtCore.QMetaObject.Connection'>
4 Connection is invalid
5 Already disconnected
The first time we click the Disconnect button the connection is valid and succesfully disconnected. The second time we click it the connection is not valid so we don’t call disconnect()
If we didn’t check the connection validity we would have got a
1 RuntimeWarning: Failed to disconnect (<PySide6.QtCore.QMetaObject.Connection object at 0x7fee46154040>) from signal "clicked()".
18.5 Connecting Multiple Slots with a Signal
You can connect a slot with more than one signals. In the example we create three slots and connect them with the button.clicked signal.
The output is
1 Executed first
2 Executed second
3 Executed third
Notice that the slots are executed in the order in which they were connected to the signal. This not necessarily true for signals and slots across different threads.
18.6 Disconnecting
In the Qt.UniqueConnection example we saw that you can break a signal-slot connection using the Signal.disconnect(receiver) method.
1 self.button.clicked.disconnect(self.on_clicked)
This breaks up the connection between button.clicked signal and the on_clicked() slot. However, QObject also has a disconnect() method with several overloads that give you more control over which signal-slot connections are removed.
static disconnect(connection)lets you pass a connection object to it. In the example we store all connection objects in theWindow.connectionslist. On clicking theDisconnect 1button all connections are disconnected in a loop:
1 def on_disconnect_1(self):
2 for c in self.connections:
3 QObject.disconnect(c)
4 self.update_label()
static disconnect(sender, signal, receiver, member)lets you specify both thesenderandthereceiverobjects as well as thesignaland thememberie the slot. On clicking theDisconnect 2button we setself.buttonas thesenderand the other three arguments toNone.Noneacts as a wildcard so this disconnects **all** signal-slot connections whereself.button` is the signal sender.
1 def on_disconnect_2(self):
2 QObject.disconnect(self.button, None, None, None)
3 self.update_label()
- On clicking the
Disconnect 3button we setself.buttonassenderandclicked(bool)assignal.receiverandmemberare still set toNone`.
1 def on_disconnect_3(self):
2 QObject.disconnect(self.button, SIGNAL('clicked(bool)'), None, None)
3 self.update_label()
This will disconnect only the slots with the signature clicked(bool) ie. Slot1 and Slot3. The connection between button.clicked and Slot2 remains.
- On clicking the
Disconnect 4button we setsendertobuttonandrecivertoself(ie to theWindowinstance).signalandmemberare set toNone. This breaks up all connections since all the slots areWindowmembers.
1 def on_disconnect_4(self):
2 QObject.disconnect(self.button, None, self, None)
3 self.update_label()
If you don’t implement
__init__()in your class at all, the parent class gets initalized automatically.↩︎
