是否可以在类定义后设置元类?

22 投票
5 回答
34121 浏览
提问于 2025-04-16 12:33

为了给一个类设置元类,我们使用 __metaclass__ 这个属性。元类是在定义类的时候使用的,所以在类定义之后再去设置这个属性是没有效果的。

当我尝试明确设置元类时,会发生这样的事情:

>>> class MetaClass(type):
    def __new__(cls, name, bases, dct):
        dct["test_var"]=True
        return type.__new__(cls, name, bases, dct)
    def __init__(cls, name, bases, dct):
        super(MetaClass, cls).__init__(name, bases, dct)


>>> class A:
    __metaclass__=MetaClass


>>> A.test_var
True
>>> class B:
    pass

>>> B.__metaclass__=MetaClass
>>> B.test_var

Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    B.test_var
AttributeError: class B has no attribute 'test_var'

我想到的最好办法是重新定义整个类,并以某种方式动态地添加 __metaclass__ 属性。或者你知道有没有更好的方法可以在类定义后设置元类呢?

5 个回答

3

你可以把元类想象成是在描述 class 语句的作用。这就是为什么在后面再设置元类是不可能的原因:类的代码已经被转换成了一种类型对象,这时候再去改变就晚了。

你可以简单地通过继承你原来的类型来添加一个元类:

class C(B):
    __metaclass__=MetaClass
6

元类的目的不是为了改变属性的访问方式,而是为了定制类的创建过程。__metaclass__这个属性定义了用来从类定义构建类型对象的可调用对象,默认是type()。所以,这个属性只在类创建时被评估。显然,在之后的任何时候改变它都没有意义,因为你无法为已经创建的类定制创建过程。想了解更多,可以查看Python语言参考,3.4.3 定制类的创建

元类显然不是你想做的事情的合适工具。如果你只是想给一个已经存在的类添加一个新属性,为什么不直接给它赋值呢?

16

你可以在创建类之后改变它的元类,就像你可以改变一个对象的类一样,不过这样会遇到很多问题。首先,最开始的元类必须和 type 不同,新的元类的 __init____new__ 方法不会被调用(不过你可以手动调用 __init__ 或者一个执行 __init__ 功能的方法)。

改变元类最简单的方法可能是从头再创建一次这个类:

B = MetaClass(B.__name__, B.__bases__, B.__dict__)

但是如果你坚持要动态改变元类,首先需要用一个临时的自定义元类来定义 B:

class _TempMetaclass(type):
    pass

class B:
    __metaclass__ = _TempMetaclass # or: type('temp', (type, ), {})

然后你可以这样定义元类:

class MetaClass(type):
    def __init__(cls, *a, **kw):
        super(MetaClass, cls).__init__(*a, **kw)
        cls._actual_init(*a, **kw)
    def _actual_init(cls, *a, **kw):
        # actual initialization goes here

接着可以做类似这样的事情:

B.__class__ = MetaClass
MetaClass._actual_init(B, B.__name__, B.__bases__, B.__dict__)

你还需要确保所有的类初始化都是通过 _actual_init 来完成的。你也可以在元类中添加一个 classmethod,这样可以帮你改变元类。

这两种解决方案都有一个小缺点,就是 B 的基类会受到限制——它们需要同时兼容原来的元类和新的元类,但我想在你的情况下这不是问题。

撰写回答