为什么我不能更改类的 __metaclass__ 属性?
我有一个奇怪的需求,想用元类来改变一个基类的 __metaclass__
,而且是在这个基类定义之后进行修改,这样它的子类就能自动使用新的 __metaclass__
。但是,这样做似乎不太管用:
class MetaBase(type):
def __new__(cls, name, bases, attrs):
attrs["y"] = attrs["x"] + 1
return type.__new__(cls, name, bases, attrs)
class Foo(object):
__metaclass__ = MetaBase
x = 5
print (Foo.x, Foo.y) # prints (5, 6) as expected
class MetaSub(MetaBase):
def __new__(cls, name, bases, attrs):
attrs["x"] = 11
return MetaBase.__new__(cls, name, bases, attrs)
Foo.__metaclass__ = MetaSub
class Bar(Foo):
pass
print(Bar.x, Bar.y) # prints (5, 6) instead of (11, 12)
我知道我这样做可能不太明智、也不被支持,甚至可能是未定义的,但我就是搞不懂为什么旧的元类还会被调用。我想至少弄明白这是怎么回事。
补充:根据 jsbueno
的建议,我把 Foo.__metaclass__ = MetaSub
这一行换成了下面这一行,结果正好达到了我想要的效果:
Foo = type.__new__(MetaSub, "Foo", Foo.__bases__, dict(Foo.__dict__))
3 个回答
子类会使用它们父类的 __metaclass__。
解决你这个问题的方法是对父类的 __metaclass__ 进行编程,使得它对父类和子类有不同的行为。你可以让它检查类字典中的一个类变量,根据这个变量的值来实现不同的行为。比如,type 就是用这种方法来决定实例是否会有一个字典,这取决于 __slots__ 是否被定义。
这个问题是关于__metaclass__
属性的,它在继承时并不会像你想的那样被使用。对于Bar
类来说,"旧的"元类也不会被调用。文档中提到关于如何找到元类的规则如下:
确定合适的元类遵循以下优先级规则:
- 如果字典中有
__metaclass__
,那么就使用它。- 否则,如果至少有一个基类,就使用它的元类(这会先查找
__class__
属性,如果找不到,就使用它的类型)。- 否则,如果存在一个名为
__metaclass__
的全局变量,就使用它。- 否则,就使用旧式的经典元类(types.ClassType)。
所以在你的Bar
类中,实际上使用的元类是从父类的__class__
属性中找到的,而不是从父类的__metaclass__
属性中找到的。
更多信息可以在这个StackOverflow的回答中找到。
一个类的元类信息在它被创建的时候就会被使用(要么是通过解析类块来创建,要么是通过明确调用元类来动态创建)。这个信息一旦创建就不能再改变,因为元类通常会在类创建时进行一些修改——创建出来的类类型就是这个元类。它的 __metaclass__
属性在创建后就不再重要了。
不过,你可以创建一个已有类的副本,并让这个副本使用不同的元类。
在你的例子中,如果你不是这样做:
Foo.__metaclass__ = MetaSub
而是这样做:
Foo = Metasub("Foo", Foo.__bases__, dict(Foo.__dict__))
那么你就能达到你想要的效果。新的 Foo
在所有方面都和之前的版本相同,但它有一个不同的元类。
不过,之前已经存在的 Foo
实例不会被视为新 Foo
的实例——如果你需要这样的效果,最好是给这个 Foo
副本起个不同的名字。