87

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
NirIzr
  • 3,131
  • 2
  • 30
  • 49
Yves Dorfsman
  • 2,684
  • 3
  • 20
  • 28

6 Answers6

29

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
renatodamas
  • 16,555
  • 8
  • 30
  • 51
Michael
  • 7,316
  • 1
  • 37
  • 63
22

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.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • @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. – jdi Jun 30 '12 at 17:46
  • You could use `class C(six.with_metaclass(MyMeta))` to make it python 3.x compatible, could you not? – TayTay Jan 10 '17 at 17:03
  • @Tgsmith61591 Maybe. I've never used the six library so I am not sure – jdi Jan 10 '17 at 18:15
18

I like doing:

class mBase1(type):
    ...

class mBase2(type):
    ...

class Base1(metaclass=mBase1):
    ...

class Base2(metaclass=mBase2):
    ...

class mChild(type(Base1), type(Base2)):
    pass

class Child(Base1, Base2, metaclass=mChild):
    ...

That way if something changes with the metaclass of the bases you don't have to worry about it. type() will take care of it.

theEpsilon
  • 1,800
  • 17
  • 30
17

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

Eg.

def function():
    pass

class MyClass(function):
    pass
Antwan
  • 2,091
  • 2
  • 21
  • 25
  • 1
    That was it! I had the error when I inherited from a module instead of a class. `base_component` instead of `base_component.BaseComponent`. – Ark-kun Jan 15 '20 at 03:11
  • I had imported my base class incorrectly into the derived class and this helped me see the silly mistake. Thanks. – joshmcode Jun 03 '20 at 17:27
  • 2
    No longer the case for python > 3.7; instead get `TypeError: function() argument 1 must be code, not str` – user2561747 Jul 07 '20 at 22:44
  • In fact, this is essentially what the problem was in OP's code (but with a module rather than a function). – Karl Knechtel Apr 01 '23 at 05:17
10

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.

Aaron B
  • 461
  • 1
  • 5
  • 21
Chickenmarkus
  • 1,131
  • 11
  • 25
  • Why doesn't python just do this for you? Seems like the metaclass version of multiple inheritance, which python handles very nicely for you. – user2561747 Jul 07 '20 at 23:39
  • 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. – Chickenmarkus Jul 09 '20 at 06:23
8

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
Danilo Bargen
  • 18,626
  • 15
  • 91
  • 127