PyQt multiple inheritance

Qt (and PyQt) does not support multiple inheritance, but when inheriting from a Qt class in two different classes it is possible to inherit from both these classes if their base class is the same or in the same line of inheritance, but there’s a couple of limitations.

1. The first inherited widget must have the deepest base-class.

2. Only additional signals defined in the first inherited widget are used.

3. Name clashes are resolved by calling the first class’s methods

1. The first inherited widget must have the deepest base-class.

So given this situation:

from PyQt4 import QtGui
class Widget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

class Frame(QtGui.QFrame):
    def __init__(self, parent=None):
        QtGui.QFrame.__init__(self, parent)

This is allowed:

class Child(Frame, Widget):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        Widget.__init__(self, parent)

But this is not:

class Child(Widget, Frame):
    def __init__(self, parent=None):
        super(Child, self).__init__(parent)

Because QFrame is not a base class of, or identical to, QWidget.

Notice that the order of the base constructors does not matter, just the order in the class definition.

2. Only additional signals defined in the first inherited widget are used.
Now extending the situation into this:

from PyQt4 import QtGui
class Widget(QtGui.QWidget):
    widgetSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

class Frame(QtGui.QFrame):
    frameSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QFrame.__init__(self, parent)

class Child(Frame, Widget):
    def __init__(self, parent=None):
        super(Child, self).__init__(parent)
        self.frameSignal.connect(self.printtest)
        self.widgetSignal.connect(self.printtest)

    def printtest(self):
        print("test")

Will raise an error at widgetSignal, stating that it is not possible to connect between a Widget signal and a unislot().

This is because to Qt we are a Frame, not a Widget. Even if both base classes had the same Qt base class (so say we make FrameA and FrameB which inherit from QFrame) it still raises that same error.

We can however add signals in our own context, so it would be possible to copy those signals and have the parent call the overrides instead of the disfunctional signals.

from PyQt4 import QtGui
class Widget(QtGui.QWidget):
    widgetSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

    def emitWidgetSignal(self):
        self.widgetSignal.emit()

class Frame(QtGui.QFrame):
    frameSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QFrame.__init__(self, parent)

    def emitFrameSignal(self):
        self.frameSignal.emit()

class Child(Frame, Widget):
    widgetSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        super(Child, self).__init__(parent)

        self.frameSignal.connect(self.printtest)
        self.widgetSignal.connect(self.printtest)

        self.emitFrameSignal()
        self.emitWidgetSignal()

    def printtest(self):
        print("test")

Now whenever Widget uses self.widgetSignal.emit(), to the Child it will refer to the Child.widgetSignal override, which we can use again.

3. Name clashes are resolved by calling the first class’s methods

As you may see in the previous example I explicitely named emitFrameSignal and emitWidgetSignal differently, this is because the first base class, Frame, is overriding the second base class, Widget.

So imagine this:

from PyQt4 import QtGui
class Widget(QtGui.QWidget):
    widgetSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

    def emitWidgetSignal(self):
        self.emitSignal()

    def emitSignal(self):
        self.widgetSignal.emit()

class Frame(QtGui.QFrame):
    frameSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QFrame.__init__(self, parent)

    def emitFrameSignal(self):
        self.emitSignal()

    def emitSignal(self):
        self.frameSignal.emit()

class Child(Frame, Widget):
    widgetSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        super(Child, self).__init__(parent)

        self.frameSignal.connect(self.printframe)
        self.widgetSignal.connect(self.printwidget)

        self.emitFrameSignal()
        self.emitWidgetSignal()

    def printframe(self):
        print("frame")

    def printwidget(self):
        print("widget")

This prints frame twice, even though we go into the frame OR widget class separately to call emitSignal, it uses ‘self’, which is in this event Child, which then uses Frame.emitSignal at all times.

So to resolve the issue we could adapt the base class either to have different function names (as shown before), or by referring to the right class when calling the method, as opposed to self. This is especially useful when the base class must be emitting signals in inherited methods which we can’t rename (such as mouse events).

from PyQt4 import QtGui
class Widget(QtGui.QWidget):
    widgetSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

    def emitWidgetSignal(self):
        Widget.emitSignal(self)

    def emitSignal(self):
        self.widgetSignal.emit()

class Frame(QtGui.QFrame):
    frameSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QFrame.__init__(self, parent)

    def emitFrameSignal(self):
        Frame.emitSignal(self)

    def emitSignal(self):
        self.frameSignal.emit()

Stay aware that this does not resolve name clashes in signal names. In fact when both base class’ signals were named ‘signal’, we could only refer to self.signal, which refers to the Frame’s signal as it is the first child.

Also the child widget’s emitSignal functions would refer to self.signal, which can not be resolved by using the class name because Frame.signal and Widget.signal refer to a pyqtSignal. When an instance is created the class signals are converted to bound signals attached to the instance by Qt. Only bound signals can be connected to and emitted (as well as all other functionality). This is also the reason signals require definition at class level, so they can be resolved and bound on init.

Now here’s a working standalone demonstration:

import sys
from PyQt4 import QtGui, QtCore

class QtStandalone:
    def __init__(self, mainfunction):
        app = QtGui.QApplication(sys.argv)
        alive = mainfunction()
        app.exec_()
        
class Widget(QtGui.QWidget):
    widgetSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

    def emitWidgetSignal(self):
        Widget.emitSignal(self)
        
    def emitSignal(self):
        self.widgetSignal.emit()

class Frame(QtGui.QFrame):
    frameSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QFrame.__init__(self, parent)

    def emitFrameSignal(self):
        Frame.emitSignal(self)
        
    def emitSignal(self):
        self.frameSignal.emit()

class Child(Frame, Widget):
    widgetSignal = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        super(Child, self).__init__(parent)
        
        self.frameSignal.connect(self.printframe)
        self.widgetSignal.connect(self.printwidget)
        
        self.emitFrameSignal()
        self.emitWidgetSignal()

    def printframe(self):
        print("frame")
        
    def printwidget(self):
        print("widget")

from Qtutils.LaunchAsStandalone import QtStandalone

def main():
    w = Child()
    w.show()
    return w
    
QtStandalone(main)

One thought on “PyQt multiple inheritance

  1. Thank you so much for this! I was beating my head against the monitor until I came across your excellent post!

Leave a Reply

Your email address will not be published. Required fields are marked *