Resolving metaclass conflicts
Asked Answered
M

6

93

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
Momism answered 30/6, 2012 at 17:15 Comment(4)
sqlite3 is a module not a "class".Edwyna
@agf: I was just looking at this and realized the same thing when you posted that.Sprig
Thanks agf, you're right! sqlite3.Connection makes it work.Momism
Possible duplicate of Triple inheritance causes metaclass conflict... SometimesTripartition
L
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
Leatriceleave answered 1/12, 2012 at 16:22 Comment(2)
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)Cutler
@SamuelMuldoon yeah, see my answer belowImena
S
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.

Sprig answered 30/6, 2012 at 17:31 Comment(3)
@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.Sprig
You could use class C(six.with_metaclass(MyMeta)) to make it python 3.x compatible, could you not?Fulkerson
@Tgsmith61591 Maybe. I've never used the six library so I am not sureSprig
A
18

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

Eg.

def function():
    pass

class MyClass(function):
    pass
Aerostat answered 1/5, 2018 at 15:10 Comment(4)
That was it! I had the error when I inherited from a module instead of a class. base_component instead of base_component.BaseComponent.Guerdon
I had imported my base class incorrectly into the derived class and this helped me see the silly mistake. Thanks.Feltie
No longer the case for python > 3.7; instead get TypeError: function() argument 1 must be code, not strHarr
In fact, this is essentially what the problem was in OP's code (but with a module rather than a function).Jinni
I
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.

Imena answered 21/4, 2020 at 18:24 Comment(0)
L
11

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.

Lenni answered 21/12, 2016 at 15:46 Comment(2)
Why doesn't python just do this for you? Seems like the metaclass version of multiple inheritance, which python handles very nicely for you.Harr
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.Lenni
R
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
Rewarding answered 12/10, 2015 at 12:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.