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 的子类将是一个更好的主意。你可以根据具体情况选择最合适的方式。