类定义后是否有设置元类的方法?

22
为了设置类的元类,我们使用 __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个回答

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的基类将受到限制-它们需要与原始元类和新元类兼容,但我想这在您的情况下不是问题。


3
谢谢你的回答,B = MetaClass(B.__name__, B.__bases__, B.__dict__) 正是我所需要的。 - Utku Zihnioglu

6
元类的目的不是修改属性访问,而是定制类的创建。`__metaclass__` 属性定义了用于从类定义构建类型对象的可调用对象,默认为 `type()` 。因此,此属性仅在类创建时评估。很明显,在任何后续时间点更改它都没有意义,因为你无法为已经创建的类自定义类创建。详情请参阅 Python 语言参考手册, 3.4.3 Customizing class creation
元类并不是完成你所需操作的正确工具。如果你只想向一个已经存在的类添加一个新属性,为什么不直接赋值呢?

3
你可以把元类看作是描述class语句的内容。这就是为什么后期设置元类是不可能的:类的代码已经被转换成了类型对象,无法再进行更改。
你可以简单地继承原始类型以添加元类:
class C(B):
    __metaclass__=MetaClass

所以我想问题应该是,如果有一种方法可以获取类型对象并对其进行修改,使得 - Utku Zihnioglu
1
@funktku:修改它怎么样?您可以向任何类型添加属性和方法,例如 B.test_var = True - Jochen Ritzel

-4
为什么你需要这个?我不相信这是可能的,但如果你能解释使用情况,我可以建议替代方案。 沿着重新定义整个类的思路,你可以这样做:
import types
NewClass = types.ClassType('NewClass', (object, ), {'__metaclass__':MetaClass})

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

for item in dir(OldClass):
    setattr(NewClass, item, getattr(OldClass, item))

将属性设置为{'__metaclass__':MetaClass}并不能实现元类。 - Utku Zihnioglu
嗯,我想这有点道理。你只需要做MetaClass(...)什么的,对吧?我从来没有尝试过那种方式使用元类。是时候进行实验了... - tangentstorm

-5

我现在无法测试它(我目前没有机器上的Python访问权限 :-(),但是除非元类是不可变的,否则您可以尝试这样做:

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

再次声明,我现在无法测试它,如果这是一个无效的答案,我很抱歉,只是想帮忙。:)


不仅它不起作用,而且问题本身就很荒谬。元类用于在类创建时挂钩某些东西,之后更改它是没有意义的。 - gb.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接