Search code examples
python-3.xmetaclassabc

Python abc inheritance with specified metaclass


What is the proper way of inheriting from ABC if class have metaclass specified?

Straightforward attempt

class KindMeta(type):
    ...

class Kind(ABC, metaclass=KindMeta):
    ...

resulted in TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Inheriting ABCMeta in KindMeta seems kind of wrong, because this mcs is also used to create nonabstract classes.

Creating custom ABC inherited from KindMeta sounds well neither.

Is there a better way of dealing with it? or which of the solutions is more pythonic and should be preferred?


Solution

  • You have to create a second metaclass, inheritng from both your orignal metaclass and the abc.ABCMeta and use that metaclass as the metaclass on the classes you want.

    If your metaclass is correctly constructed, using super() calls in all (special) methods it implements, it as straightforward as:

    import abc 
    ...
    
    class KindMeta(type):
        ...
    
    class CombinedMeta(KindMeta, abc.ABCMeta):
        pass
    
    class Kind(ABC, metaclass=CombinedMeta):
        ...
    

    If your metaclass is not making use of super(), but calling hardcoded type methods, you have to change it to do so. For some methods like __prepare__ and __call__, it makes sense to not call the correspondent super() method depending on what you are doing, and I think there is no need to run the correspondent methods in ABCMeta. And, of course, that is only needed if you don't want or can't change the metaclass you already have, or if you want your metaclass to be used in other classes that are not using ABC -

    Otherwise, you can just make your own metaclass inherit from ABCMeta itself - no need to create a third metaclass to combine both:

    import abc 
    ...
    
    class KindMeta(abc.ABCMeta):
        ...
    

    If, on the other hand, you are using the metaclass + class construction machinery of Python to create objects that are "not quite classes" (like for example the zope.interface package does to create interfaces), you have to decide (1) if it makes sense to use that along abc.ABC at all, and second, if the correspondent method in the ABCMeta have to be run (usually yes, if you need the functionality). In that case you have to customize your metaclass appropriately - which may include to force combining the classes with multiple-inheritance (even if you could just inherit from ABCMeta) to prevent it from calling type.__new__ (if that is your intent):

    class KindMeta(type):
        def __new__(mcls, name, bases, ns, **kw):
            cls = ExistingClassRegistry[name]
            return cls
    
    class CombinedMeta(abc.ABCMeta, KindMeta):
        # note the reversed order - ABCMeta will run first
        # and call super().__new__ which runs KindMeta.__new__ and
        # does not forward to type.__new__  
        pass