1

I'd like to make class that derives from PyQt5 QtWidget.QWidget and an abc.ABCMeta. Both these classes have their own meta class as type so, according to this page and this SO question, I need to create my own meta class that derives from the meta classes of QWidget and abc.ABCMeta, and explicitly use that as metaclass for my class.

So far so good, I've defined an QtAbcMeta class and used this as metaclass for my ConcreteWidget class (see below).

import abc
from PyQt5 import QtWidgets, QtCore


class AbstractClass(metaclass=abc.ABCMeta):

    def __init__(self, name):
        self._name = name

    @abc.abstractmethod
    def myMethod():
        pass


class QtAbcMeta(type(QtWidgets.QWidget), type(AbstractClass)):
    pass


class ConcreteWidget(QtWidgets.QWidget, AbstractClass, metaclass=QtAbcMeta):

    def __init__(self, name, parent=None):
        AbstractClass.__init__(self, name)
        QtWidgets.QWidget.__init__(self, parent=parent)  # This gives a type error.


    def myMethod():
        print("My widget does something")


def main():
    app = QtWidgets.QApplication([])
    myWidget = ConcreteWidget('my name', parent=None)

if __name__ == "__main__":
    main()

However, when I try to call the __init__ method of the QtWidgets.QWidget method, to set the parent, I get the following TypeError:

Traceback (most recent call last):
  File "main.py", line 36, in <module>
    main()
  File "main.py", line 33, in main
    myWidget = ConcreteWidget('my name', parent=None)
  File "main.py", line 24, in __init__
    QtWidgets.QWidget.__init__(self, parent=parent)  # This gives a type error.
TypeError: __init__() missing 1 required positional argument: 'name'

I have no idea what's wrong here. Has the signature of QtWidgets.QWidget.__init__ changed somehow? Any help would be appreciated.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
titusjan
  • 5,376
  • 2
  • 24
  • 43
  • What is the purpose of `AbstractClass.__init__(self, name)`? The `AbstractClass` instance is not saved anywhere within `ConcreteWidget`. – John Gordon Oct 22 '18 at 15:07
  • In this MVCE the line `AbstractClass.__init__(self, name)` indeed does nothing. In my actual use case `ConcreteWidget` inherits from some `ConcreteClass`, that, in turn, inherits from `AbstractClass`. The `__init__` method of `ConcreteClass` expected the `name` parameter. – titusjan Oct 22 '18 at 15:58

1 Answers1

1

QT classes are designed to be used in modern Python code in a cooperative way - and that means they internally make use of Python's super(), to ensure all the proper methods in all superclasses are called with their respective parameters.

The creation of super() has come around exactly to avoid the need to hard-code calls to super-classes __init__ (and other overriden methods) with the super-class names, and so that you don't have to worry about the calling order.

The only thing is that each class that is intended to be used as multi-class mixin have to "know" how it about it, extract the named parameters it uses, and call super again with the remaining parameters. QT code goes one step further, and checks if all the named parameters for the other classes are presented to it, otherwise it errors.

More than that, since Qt classes themselves make use of super, that means your __init__ in abstract class is being called twice. It won't make a difference in this simple code, but might be a problem in more complicated base classes.

So, just rewrite your __init__ method to do it in the "pythonic" way:

class ConcreteWidget(QtWidgets.QWidget, AbstractClass, metaclass=QtAbcMeta):

    def __init__(self, name, parent=None):
        super().__init__(name=name, parent=parent)
jsbueno
  • 99,910
  • 10
  • 151
  • 209