I'm trying to combine a class implementing an abc interface with another class.

Let's start with abc simple example:

import abc

class Interface(abc.ABC):
    @abc.abstractmethod
    def pure_virtual_func(self):
        raise NotImplementedError

class Impl(Interface):
    def __init__(self):
        pass

impl = Impl()

This piece of code will fail when impl is created: Can't instantiate abstract class Impl with abstract methods pure_virtual_func. This is great, that's what you expect.

Now, when Impl needs to derive from another class:

import abc
from PySide6.QtWidgets import QApplication, QWidget

class Interface(abc.ABC):
    @abc.abstractmethod
    def pure_virtual_func(self):
        raise NotImplementedError
    
class Impl(QWidget,Interface):
    def __init__(self):
        pass
    
app = QApplication()
impl = Impl()

You get the error: Error: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases.

According to some posts (Resolving metaclass conflicts and Abstract class inheriting from ABC and QMainWindow), it is recommended to do that:

import abc
from PySide6.QtWidgets import QApplication, QWidget

class Interface(abc.ABC):
    @abc.abstractmethod
    def pure_virtual_func(self):
        raise NotImplementedError
    
class Meta(type(QWidget), type(Interface)):
    pass
    
class Impl(QWidget, Interface, metaclass=Meta):
    def __init__(self):
        pass
    
app = QApplication()
impl = Impl()

OK, now the metaclass error is gone. But you don't get the error Can't instantiate abstract class Impl with abstract methods pure_virtual_func anymore, now the impl is simply created, and you'll get the NotImplementedError exception if pure_virtual_func is called.

The abc great feature detecting not implemented pure virtual method is gone, how can one derive from Interface and another class (QWidget in my exemple) and preserve not implemented pure virtual method detection?

There's many posts explaining how to make the code work with metaclass stuff, but I found none that preserves abc capability to detect not implemented pure virtual methods.

1

There are 1 best solutions below

4
glory9211 On

Short Description

The behavior you are observing is due to the implementation of your new Meta Class bypassing the abstract method checking behavior of ABCMeta metaclass for any abc abstract class you use which is different from the metaclass QMetaObject for QWidget.

Recommendation Instead of using inheritance, you can try composition refactor your design to avoid using this method. Python allows multiple parents for a class which leads to the diamond problem if not careful. But still if you desire to achieve the behavior requested you due to some constraints you can try the following.

Code

import abc
from PySide6.QtWidgets import QApplication, QWidget
import inspect

class Interface(abc.ABC):
    @abc.abstractmethod
    def pure_virtual_func(self):
        raise NotImplementedError
    
class Meta(type(Interface),type(QWidget)):
    pass

    
class Impl(Interface, QWidget, metaclass=Meta):
    def __init__(self):
        self._check_abstract_methods()
    
    def _check_abstract_methods(self):
        for name, value in inspect.getmembers(type(self), predicate=inspect.isfunction):
            if getattr(value, "__isabstractmethod__", False):
                raise NotImplementedError(f"The abstract method '{name}' is not implemented in '{type(self).__name__}'.")
    
app = QApplication()
new_impl = Impl()

Using the inspect module we explicitly check for all memebers of the class if they are an abstract method. If True we throw an exception similar to the abc module.