Resolving metaclass conflicts

86,013

Solution 1

Instead of using the recipe as mentioned by jdi, you can directly use:

class M_C(M_A, M_B):
    pass

class C(A, B):
    __metaclass__ = M_C

Solution 2

Your example using sqlite3 is invalid because it is a module and not a class. I have also encountered this issue.

Heres your problem: The base class has a metaclass that is not the same type as the subclass. That is why you get a TypeError.

I used a variation of this activestate snippet using noconflict.py. The snippet needs to be reworked as it is not python 3.x compatible. Regardless, it should give you a general idea.

Problem snippet

class M_A(type):
    pass
class M_B(type):
    pass
class A(object):
    __metaclass__=M_A
class B(object):
    __metaclass__=M_B
class C(A,B):
    pass

#Traceback (most recent call last):
#  File "<stdin>", line 1, in ?
#TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass #of the metaclasses of all its bases

Solution snippet

from noconflict import classmaker
class C(A,B):
    __metaclass__=classmaker()

print C
#<class 'C'>

The code recipe properly resolves the metaclasses for you.

Solution 3

This also happens when you try to inherit from a function and not a class.

Eg.

def function():
    pass

class MyClass(function):
    pass

Solution 4

To use the pattern described by @michael, but with both Python 2 and 3 compatibility (using the six library):

from six import with_metaclass

class M_C(M_A, M_B):
    pass

class C(with_metaclass(M_C, A, B)):
    # implement your class here

Solution 5

As far as I understood from the previous answers the only things we usually have to do manually are:

class M_A(type): pass
class M_B(type): pass
class A(metaclass=M_A): pass
class B(metaclass=M_B): pass

class M_C(M_A, M_B): pass
class C:(A, B, metaclass=M_C): pass

But we can automate the last two lines now by:

def metaclass_resolver(*classes):
    metaclass = tuple(set(type(cls) for cls in classes))
    metaclass = metaclass[0] if len(metaclass)==1 \
                else type("_".join(mcls.__name__ for mcls in metaclass), metaclass, {})   # class M_C
    return metaclass("_".join(cls.__name__ for cls in classes), classes, {})              # class C

class C(metaclass_resolver(A, B)): pass

Since we do not use any version-specific metaclass syntax this metaclass_resolver works with Python 2 as well as Python 3.

Share:
86,013
Yves Dorfsman
Author by

Yves Dorfsman

Updated on December 01, 2021

Comments

  • Yves Dorfsman
    Yves Dorfsman over 2 years

    I need to create a class that uses a different base class depending on some condition. With some classes I get the infamous:

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

    One example is sqlite3, here is a short example you can even use in the interpreter:

    >>> import sqlite3
    >>> x = type('x', (sqlite3,), {})
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
    
  • jdi
    jdi almost 12 years
    @JBernardo: Thanks. I commented on it not being py3 compat. It has a few other issues in the snippet that make it not work as well.
  • TayTay
    TayTay over 7 years
    You could use class C(six.with_metaclass(MyMeta)) to make it python 3.x compatible, could you not?
  • jdi
    jdi over 7 years
    @Tgsmith61591 Maybe. I've never used the six library so I am not sure
  • Samuel Muldoon
    Samuel Muldoon over 4 years
    In python 2 you use that __metaclass__ stuff. How would you do this in python 3? Would it work to simply have class C(A, B, metaclass = M_C)
  • Ark-kun
    Ark-kun over 4 years
    That was it! I had the error when I inherited from a module instead of a class. base_component instead of base_component.BaseComponent.
  • theEpsilon
    theEpsilon about 4 years
    @SamuelMuldoon yeah, see my answer below
  • joshmcode
    joshmcode almost 4 years
    I had imported my base class incorrectly into the derived class and this helped me see the silly mistake. Thanks.
  • user2561747
    user2561747 almost 4 years
    No longer the case for python > 3.7; instead get TypeError: function() argument 1 must be code, not str
  • user2561747
    user2561747 almost 4 years
    Why doesn't python just do this for you? Seems like the metaclass version of multiple inheritance, which python handles very nicely for you.
  • Chickenmarkus
    Chickenmarkus almost 4 years
    The method resolution order (MRO) can differ between the base and meta classes. Hence, python let you distinguish between the actual MROs. Thus, you have to take care that the new common meta class creates a valid new class from the base classes yourself.