Python 2.7 从元类继承

0 投票
1 回答
1233 浏览
提问于 2025-04-21 10:09

我有一个简单的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 个回答

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)的错误。

撰写回答