17

为了设置一个类的元类,我们使用__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__以某种方式动态添加属性。或者您知道在类定义之后设置元类的更好方法吗?

4

5 回答 5

13

您可以在创建类后更改元类,就像更改对象的类一样,但是您会遇到很多问题。对于初学者,初始元类需要与 不同type,新元类的__init__and__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 的基础会受到限制 - 它们需要与原始元类和新元类兼容,但我想这对你来说不是问题。

于 2011-02-25T18:55:07.423 回答
5

元类的目的不是修改属性访问,而是自定义类的创建。该__metaclass__属性定义了用于从类定义构造类型对象的可调用对象,默认为type(). 因此,该属性仅在类创建时评估。显然在以后的任何时候更改它都没有任何意义,因为您无法为已经创建的类自定义类创建。有关详细信息,请参阅Python 语言参考,3.4.3 自定义类创建

对于您显然想要做的事情,元类根本不是正确的工具。如果您只是想为已经存在的类添加一个新属性,为什么不直接分配它呢?

于 2011-02-25T18:21:56.880 回答
2

您可以认为元类描述了class语句的作用。这就是为什么稍后设置元类是不可能的:类代码已经转换为类型对象,更改它为时已晚。

您可以简单地将原始类型子类化以添加元类:

class C(B):
    __metaclass__=MetaClass
于 2011-02-25T17:52:57.763 回答
-4

你为什么需要这个?我不相信这是可能的,但如果你能解释用例,我可能会建议一个替代方案。

沿着重新定义整个班级的路线,你可以做的是:

import types
NewClass = types.ClassType('NewClass', (object, ), {'__metaclass__':MetaClass})

然后只需复制所有旧方法:

for item in dir(OldClass):
    setattr(NewClass, item, getattr(OldClass, item))
于 2011-02-25T17:53:21.937 回答
-6

我现在无法对此进行测试(我现在无法在我的机器上访问 Python :-(),但除非元类是不可变的,否则你可以尝试这样的事情:

>>> class B:
        pass
>>> setattr(B, "__metaclass__", MetaClass)

同样,我现在无法测试它,所以如果这是一个无效的答案,我很抱歉,只是想提供帮助。:)

于 2011-02-25T17:48:49.647 回答