Python 2.7 从元类继承
我有一个简单的Python例子,里面定义了一个内联元类,作为函数和作为类的表现是不同的,我想搞明白这是为什么:
>>> class Test(object):
... def __metaclass__(name, bases, nmspc):
... cls = type(name, bases, nmspc)
... def __init__(self, field, *args, **kwargs):
... print "making " + field
... super(cls, self).__init__()
... cls.__init__ = __init__
... return cls
...
>>> t = Test('lol')
making lol
>>> class Test2(Test): pass
...
>>> t = Test2('lol')
making lol
和:
>>> class Test(object):
... class __metaclass__(type):
... def __init__(cls, name, bases, nmspc):
... type.__init__(cls, name, bases, nmspc)
... def __init__(self, field, *args, **kwargs):
... print "making " + field
... super(cls, self).__init__()
... cls.__init__ = __init__
...
>>> t = Test('lol')
making lol
>>> class Test2(Test): pass
...
>>> t2 = Test2('lol')
making lol
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __init__
TypeError: __init__() takes at least 2 arguments (1 given)
那么,定义一个类和定义一个函数到底有什么不同呢?它们最终都会通过稍微不同的方式返回一个类,因为修改过的cls.__init__在一种情况下会被继承,而在另一种情况下则不会。
我还检查了类似的构造,分别在外面定义了一个函数和一个类,结果也是一样的表现。
1 个回答
如果你仔细阅读关于元类的文档,你会发现,当Python检查一个类的继承元类时,它并不会查看基类的__metaclass__
属性,而是查看它的__class__
或类型(因为一般来说,一个类的元类就是它的类)。(你可以在build_class函数中看到实现这一行为的C代码。)
你的函数版本是通过调用type
来构建类,然后再进行修改。但这个类的类仍然设置为type
,因为它就是在这里被创建的。
而你的类版本则是继承了type
,所以它自己成为了所创建类的__class__
。
你可以看到这个区别(在这里我把你的函数版本叫做TestF
,把类版本叫做TestC
,并从一个名为mc
的模块中导入它们):
In [7]: TestC.__class__
Out[7]: mc.__metaclass__
In [8]: TestF.__class__
Out[8]: type
In [9]: type(TestC)
Out[9]: mc.__metaclass__
In [10]: type(TestF)
Out[10]: type
由于这个区别,当你从TestC
继承时,__metaclass__
被用作新子类的元类,而当你从TestF
继承时,则使用type
。
为什么会导致观察到的差异呢?因为在TestF
的情况下,由于在创建子类时没有应用自定义的元类,所以子类没有得到修改过的__init__
方法。因此被调用的__init__
方法仍然是TestF.__init__()
,而当它调用super(cls, self).__init__()
时,实际上是在调用object.__init__()
,这个方法不需要任何参数,所以一切正常。
但是在TestC
的情况下,当你创建子类时又使用了元类。(如果你在每个版本的cls.__init__ = __init__
行之前插入一个print
语句,你就能观察到这个区别。)所以子类得到了自己的自定义__init__
方法。因此在这种情况下,当它调用super(cls, self).__init__()
时,实际上是在调用TestC.__init__()
,而这个方法(本身是由元类创建的)需要一个必需的参数field
,而你并没有传递这个参数。因此你会遇到TypeError: __init__() takes at least 2 arguments (1 given)
的错误。