Metaclass conflict when trying to create a Python abstract class that also subclasses a PySide6 class
Asked Answered
C

1

8

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?

Ceresin answered 11/3, 2021 at 22:54 Comment(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.Calvinism
@Calvinism 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.Ceresin
If you don't actually need metaclasses, can't you just inherit from a basic python object that implements the methods you require?Calvinism
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.Ceresin
J
5

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):...
Jonniejonny answered 13/7, 2023 at 16:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.