Context
I started exploring the concept of Metaclass with python. Rapidly, I found myself facing a common problem.
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
From my understanding, this happens when you create a class [C] that inherits from two classes (or more) [A, B] that does not share the same metaclass [M_A, M_B]
M_A M_B
: :
: :
A B
\ /
\ /
C
The problem is well described here and the solution is simple. We need to create a new metaclass [M_C] that inherits from both M_A and M_B
I tried to make this process automatic by creating a method that creates [M_C] dynamically. Something like this
My problem
My class [C] inherits from [B] and I want to use [M_A] has its metaclass,
M_A is a custom metaclass (singleton)
B's metaclass is abc.ABCMeta
My metaclass_resolver()
successfully creates a new metaclass [M_C]
however it inherits from abc.ABCMeta
and type
instead of inheriting from abc.ABCMeta
and M_A
.
from collections import UserDict
def metaclass_resolver(*classes):
metaclasses = tuple(set(type(cls) for cls in classes))
new_metaclass = metaclasses[0]
new_meta_name = new_metaclass.__name__
#if there's more than one metaclass
#combine them and create a new metaclass (M_C)
if len(metaclasses) > 1:
#get the name of each metaclass
new_meta_name = "_".join(mcls.__name__ for mcls in metaclasses)
#create a new dynamic class
# type('name','bases {inheritance}, attrs)'
new_metaclass = type(new_meta_name, metaclasses, {})
return new_metaclass(new_meta_name, metaclasses, {})
#my custom metaclass (singleton)
class M_A(type):
def __new__(cls, name, bases, attrs):
c = super().__new__(cls, name, bases, attrs)
return c
def __init__(cls, name, bases, attrs):
#When this metaclass is initiated, no instance of the class
#using this metaclass would have been created
cls.__instance = None
super().__init__(name, bases, attrs)
def __call__(cls, *args, **kwargs):
#get the saved instance
instance = cls.__instance
#if the instance does not exists
if instance is None:
#create one
instance = cls.__new__(cls)
instance.__init__(*args, **kwargs)
#save it
cls.__instance = instance
return instance
pass
#abc metaclass
class B(UserDict):
def method_needed_in_C(self):
pass
#my class
class C(B, metaclass = metaclass_resolver(M_A, B)):
def __init__(self):
super().__init__()
print(type(self.__class__))
#<class 'abc.ABCMeta_type'>
pass
if __name__ == "__main__":
c = C()
In metaclass_resolver()
when I use type(cls)
it returns <class 'type'>
instead of <class 'M_A'>
which make sense since every class derived from type
. But then, how can I point directly to <class 'M_A'>
?
If I use cls
directly I get this error :
TypeError: descriptor '__init__' requires a 'type' object but received a 'str'
Thanks!