Python 元类的继承

111 阅读3分钟

Python 中的元类可以是任何可调用对象,通常情况下,我们可以直接使用一个函数作为元类,也可以使用一个继承自 type 的类作为元类。这两种方式在很多情况下都可以实现相同的功能,那么什么时候应该使用继承自 type 的类作为元类呢?

2、解决方案 使用继承自 type 的类作为元类有一些细微的差异,主要与类的继承有关。当使用函数作为元类时,生成的类实际上是 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

print(type(c1))
# <type 'type'>

class sub1(c1):
    pass

print(type(sub1))
# <type 'type'>

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

try:
    class sub2(c1, c2):
        pass
except TypeError as e:
    print(e)
# 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

注意,在定义 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

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

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

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

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

try:
    class Sub2(C1, C2):
        pass
except TypeError as e:
    print(e)
# 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

print(type(C3))
# <class '__main__.M3'>

这就是为什么我在元类 new 方法中使用 super() 的原因:这样每个元类都可以调用其在 MRO 中的下一个元类。

在某些用例中,你可能需要你的类是 type 类型,或者你可能想要避免继承问题,在这种情况下,使用元类函数可能是更好的选择。在其他情况下,类的类型可能真的很重要,或者你可能想要操作所有的子类,在这种情况下,使用 type 的子类将是一个更好的主意。你可以根据具体情况选择最合适的方式。