解决元类冲突

88

我需要创建一个类,根据某些条件使用不同的基类。对于某些类,我得到了臭名昭著的:

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

其中一个例子是sqlite3,这里是一个简短的示例,甚至可以在解释器中使用:

>>> 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

9
sqlite3 是一个模块而不是一个“类”。 - agf
@agf:我刚看到这个,当你发帖时意识到了同样的问题。 - jdi
谢谢 agf,你是对的!sqlite3.Connection 让它工作。 - Yves Dorfsman
可能是[三重继承导致元类冲突...有时候]的重复问题。(https://dev59.com/tmw15IYBdhLWcg3whMK_) - Wernight
6个回答

29

不必使用jdi提到的食谱,您可以直接使用以下内容:

class M_C(M_A, M_B):
    pass

class C(A, B):
    __metaclass__ = M_C

4
在Python 2中,你需要使用__metaclass__。那么在Python 3中该怎么做呢?你可以像这样简单地写:class C(A, B, metaclass=M_C),这样就能指定元类了。 - Samuel Muldoon
2
@SamuelMuldoon 是的,看一下我的答案下面 - theEpsilon

22
您使用的sqlite3示例无效,因为它是一个模块而不是一个类。我也遇到过这个问题。
您的问题在于:基类具有与子类不同类型的元类。这就是为什么会出现TypeError的原因。
我使用了此 activestate 代码片段中的 noconflict.py 变体。该代码片段需要重新设计,因为它不兼容Python 3.x。但无论如何,它应该能给您一个大致的概念。 问题代码片段
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

解决方案片段

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

print C
#<class 'C'>

这段代码可以正确地为您解析元类。


@JBernardo:谢谢。我评论说它不兼容py3。代码片段中还有一些其他问题,使其无法正常工作。 - jdi
你可以使用 class C(six.with_metaclass(MyMeta)) 来使其兼容 Python 3.x,不是吗? - TayTay
@Tgsmith61591 可能吧。我从未使用过six库,所以不确定。 - jdi

18

当您尝试从函数而不是类继承时,这也会发生。

例如:

def function():
    pass

class MyClass(function):
    pass

1
就是这样!我在继承模块而不是类时出现了错误。使用了 base_component 而不是 base_component.BaseComponent - Ark-kun
我将基类错误地导入到派生类中,这帮助我看到了愚蠢的错误。谢谢。 - joshmcode
2
Python > 3.7不再是这种情况了;相反会得到TypeError: function() argument 1 must be code, not str - user2561747
事实上,这基本上就是 OP 代码中的问题(但是使用的是模块而不是函数)。 - Karl Knechtel

18

我喜欢做的事情:

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):
    ...

如果基类的元类发生改变,你无需担心。 type() 会处理它。


10

从之前的回答中我所理解的是,我们通常需要手动完成的事情只有:

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

现在我们可以通过自动化完成最后两行,方法如下:
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

由于我们不使用任何特定版本的元类语法,因此此 metaclass_resolver 可用于 Python 2 和 Python 3。


为什么 Python 没有为你自动处理这个问题呢?这似乎是多重继承的元类版本,而 Python 对此非常友好。 - user2561747
方法解析顺序(MRO)可能因基类和元类而异。因此,Python 允许您区分实际的 MRO。因此,您必须注意,新的公共元类会从基类自动生成一个有效的新类。 - Chickenmarkus

8

如果您想使用@michael描述的模式,并且希望同时兼容Python 2和3(可以使用six库):

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接