习惯从type继承元类吗?

37 投票
1 回答
10487 浏览
提问于 2025-04-15 18:33

我一直在尝试理解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会更合适。可以根据具体情况选择最合适的风格。

撰写回答