Why are signals defined as class variables?

Is there any reason why signals of WorkerSignals are class variables?

This is actually due to how PyQt5 (and PySide2) hook up signals and create Qt objects, and how the order of that relates to their related Python objects.

When you define a signal on a class, e.g.

class MyWindow(QMainWindow):
     
    my_custom_signal = pyqtSignal()

You are creating an instance of pyqtSignal and assigning it to the class variable my_custom_signal. However, nothing is happening in Qt here, this is just a simple Python object.

When you create an instance of your MyWindow class the Python object is created, the __init__ is run, the super() class is called and then PyQt5 creates the Qt objects, and hooks everything together. At this point PyQt5 searches for signal definitions on the object, and hooks these up using Qt signals (with appropriate magic).

If you defined signals in your __init__ like you might expect…

class MyWindow(QMainWindow):
     
    def __init__(self):
         super().__init__()
         self.my_custom_signal = pyqtSignal()

this won’t work… because when you call the super init, PyQt5 will try and hook up the signals, but you haven’t defined them yet.

Of course, PyQt5 could require you have to put the super().__init__ call after you’ve created the signals…

class MyWindow(QMainWindow):
     
    def __init__(self):
         self.my_custom_signal = pyqtSignal()
         super().__init__()

…but that’s surprising and not very nice.

Putting them on the class side-steps all this.