惯例继承元类从type吗?

37

我一直在尝试理解Python元类,因此一直在查看一些示例代码。据我所知,Python元类可以是任何可调用对象。因此,我可以像下面这样定义我的元类:

def metacls(clsName, bases, atts):
    ....
    return type(clsName, bases, atts)

然而,我看到很多人以以下方式编写他们的元类:

class Metacls(type):
    def __new__(meta, clsName, bases, atts):
        ....
        return type.__new__(meta, clsName, bases, atts)

据我所知,这两个代码会实现相同的功能。使用基类是否有任何原因?这是惯例吗?

1个回答

47

有细微的差异,主要涉及到继承。当使用函数作为元类时,生成的类实际上是type的一个实例,可以无限制地从其继承;但是,这样的子类将不会调用元类函数。当使用type的子类作为元类时,生成的类将是该元类的实例,其任何子类也将是其元类的实例;然而,多重继承将受到限制。

举例说明这些差异:

>>> def m1(name, bases, atts):
>>>     print "m1 called for " + name
>>>     return type(name, bases, atts)
>>>

>>> def m2(name, bases, atts):
>>>     print "m2 called for " + name
>>>     return type(name, bases, atts)
>>>

>>> class c1(object):
>>>     __metaclass__ = m1
m1 called for c1

>>> type(c1)
<type 'type'>

>>> class sub1(c1):
>>>     pass

>>> type(sub1)
<type 'type'>

>>> class c2(object):
>>>     __metaclass__ = m2
m2 called for c2

>>> class sub2(c1, c2):
>>>     pass

>>> type(sub2)
<type 'type'>
请注意,在定义sub1和sub2时,没有调用元类函数。它们将被创建,就好像c1和c2没有元类一样,但是在创建后进行了操作。
>>> class M1(type):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M1 called for " + name
>>>         return super(M1, meta).__new__(meta, name, bases, atts)

>>> class C1(object):
>>>     __metaclass__ = M1
M1 called for C1

>>> type(C1)
<class '__main__.M1'>

>>> class Sub1(C1):
>>>     pass
M1 called for Sub1

>>> type(Sub1)
<class '__main__.M1'>

注意这里已经有了一些区别:在创建Sub1时调用了M1,而且两个类都是M1的实例。我在这里使用super()进行实际创建,原因稍后会变得清晰。

>>> class M2(type):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M2 called for " + name
>>>         return super(M2, meta).__new__(meta, name, bases, atts)

>>> class C2(object):
>>>     __metaclass__ = M2
M2 called for C2

>>> type(C2)
<class '__main__.M2'>

>>> class Sub2(C1, C2):
>>>     pass
M1 called for Sub2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 23, in __new__
TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

这是在使用元类时多重继承的主要限制。Python不知道M1和M2是否兼容,因此它强制你创建一个新的元类来确保它执行你需要的操作。

>>> class M3(M1, M2):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M3 called for " + name
>>>         return super(M3, meta).__new__(meta, name, bases, atts)

>>> class C3(C1, C2):
>>>     __metaclass__ = M3
M3 called for C3
M1 called for C3
M2 called for C3

>>> type(C3)
<class '__main__.M3'>
这就是为什么我在元类的 __new__ 函数中使用了 super():这样每个函数都可以在 MRO 中调用下一个函数。
某些情况下,您的类可能需要是 type 类型,或者可能希望避免继承问题,在这种情况下,元类函数可能是最好的选择。在其他情况下,类的类型可能确实很重要,或者您可能希望操作所有子类,在这种情况下,继承自 type 会是更好的选择。请根据具体情况自由选择适合的风格。

1
感谢您抽出时间解释这个问题。 - Craig McQueen
1
非常感谢,很少见到如此清晰明了的答案。 - Nikwin

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