6

I'm trying to create an abstract base class that also inherits an arbitrary PySide6 class. However, the following produces the error TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases.

from abc import ABC, abstractmethod

from PySide6.QtWidgets import QApplication, QWidget


class MyBaseWidget(QWidget, ABC):

    def __init__(self, parent=None):
        super().__init__(parent=parent)
    
    @abstractmethod
    def foo(self):
        pass


class MyConcreteWidget(MyBaseWidget):

    def __init__(self, parent=None):
        super().__init__(parent=parent)


app = QApplication([])
widget = MyConcreteWidget()
widget.show()
app.exec_()

I tried to resolve this using the solution seen below (inspiration came from Resolving metaclass conflicts, http://www.phyast.pitt.edu/~micheles/python/metatype.html, Multiple inheritance metaclass conflict, etc.).

class MyMeta(ABCMeta, type(QWidget)): pass


class MyBaseWidget(QWidget, metaclass=MyMeta):

    def __init__(self, parent=None):
        super().__init__(parent=parent)
    
    @abstractmethod
    def foo(self):
        pass


class MyConcreteWidget(MyBaseWidget):

    def __init__(self, parent=None):
        super().__init__(parent=parent)


app = QApplication([])
widget = MyConcreteWidget()
widget.show()
app.exec_()

This executes without error, but I was expecting an error like TypeError: Can't instantiate abstract class MyConcreteWidget with abstract methods foo when instantiating MyConcreteWidget. Not being able to enforce the base class's interface really takes away the benefit of having an abstract base class. Any solutions?

physhics
  • 61
  • 4
  • If I may, why do you need a metaclass for a QWidget? There are few cases for which it's advisable to do that, and in most of them I realized that it's usually not worth it, as it makes things complex and prone to issues hard to track and solve. – musicamante Mar 12 '21 at 04:01
  • @musicamante the main goal is to create a customized abstract `QWidget` class (`QWidget` is arbitrary...it could be any Qt class) so that others that inherit it are required to implement the abstract method(s). The problem doesn't necessarily require the use of metaclasses, but `ABCMeta` is conflicting with `QWidget`'s metaclass so my original thought was that I needed to solve that metaclass conflict. – physhics Mar 12 '21 at 15:12
  • If you don't actually need metaclasses, can't you just inherit from a basic python object that implements the methods you require? – musicamante Mar 12 '21 at 15:19
  • All concrete widgets need to implement `foo` in their own way, which is why it's abstract in `MyBaseWidget`; there is no default implementation of `foo`. The metaclass conflict is a side effect of what I'm trying to achieve since Python `ABC` class don't play well with Qt classes. – physhics Mar 12 '21 at 15:34

1 Answers1

0

This following solution gives the desired result for me.

First get the Classes and their Metaclasses you want to extend

from abc import ABC, ABCMeta
from PySide6.QtCore import QObject

QObjectMeta = type(QObject)

Then make your CustomMetaClass from the Metaclass of the two Classes you want.

class _ABCQObjectMeta(QObjectMeta, ABCMeta):...

Lastly, make the InterfaceClass by inheriting the two Classes and setting the metaclass to the CustomMetaclass

class ABCQObject(QObject, ABC, metaclass=_ABCQObjectMeta):...

This process can be applied to any class you want.

Final Code:-

from abc import ABC, ABCMeta

from PySide6.QtCore import QObject
from PySide6.QtWidgets import QWidget

QObjectMeta = type(QObject)
QWidgetMeta = type(QWidget)

class _ABCQObjectMeta(QObjectMeta, ABCMeta):...
class _ABCQWidgetMeta(QObjectMeta, ABCMeta):...


class ABCQObject(QObject, ABC, metaclass=_ABCQObjectMeta):...
class ABCQWidget(QWidget, ABC, metaclass=_ABCQWidgetMeta):...

Now you can inherit this Abstract class like you would do with ABC eg:-

from abc import abstractmethod
import ABCQWidget


class BaseView(ABCQWidget):

    @abstractmethod
    def setupManager(self, manager):...

    @abstractmethod
    def updateContent(self, content):...