是否可以在类定义后设置元类?
为了给一个类设置元类,我们使用 __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 个回答
你可以把元类想象成是在描述 class
语句的作用。这就是为什么在后面再设置元类是不可能的原因:类的代码已经被转换成了一种类型对象,这时候再去改变就晚了。
你可以简单地通过继承你原来的类型来添加一个元类:
class C(B):
__metaclass__=MetaClass
元类的目的不是为了改变属性的访问方式,而是为了定制类的创建过程。__metaclass__
这个属性定义了用来从类定义构建类型对象的可调用对象,默认是type()
。所以,这个属性只在类创建时被评估。显然,在之后的任何时候改变它都没有意义,因为你无法为已经创建的类定制创建过程。想了解更多,可以查看Python语言参考,3.4.3 定制类的创建。
元类显然不是你想做的事情的合适工具。如果你只是想给一个已经存在的类添加一个新属性,为什么不直接给它赋值呢?
你可以在创建类之后改变它的元类,就像你可以改变一个对象的类一样,不过这样会遇到很多问题。首先,最开始的元类必须和 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 的基类会受到限制——它们需要同时兼容原来的元类和新的元类,但我想在你的情况下这不是问题。