【问题标题】:Python 2.7 inheritance from metaclassesPython 2.7 从元类继承
【发布时间】:2025-12-08 16:15:01
【问题描述】:

我有一个玩具 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__ 以一种方式被继承,而不是另一种。

我还通过在外部进行函数和类声明来检查类似的构造,我得到了相同的行为。

【问题讨论】:

  • 您的问题似乎是 “为什么不同的事物会有不同的行为?” - 您能澄清一下您的预期吗?
  • 我期待 __metaclass__ 的两个定义的子类获得相同的 __init__ 方法。

标签: python metaclass


【解决方案1】:

如果你仔细阅读documentation on metaclasses,你会注意到,当 Python 检查继承的元类时,它不会查看基类的 __metaclass__ 属性,而是寻找它的 __class__ 或类型(因为一般来说,一个类的元类就是它的类)。 (您可以在 the build_class function 中看到实现此行为的 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) 错误。

【讨论】: