对于元类,我有一个奇怪且不寻常的用例,我想在定义基类之后更改它的__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__))
答案 0 :(得分:3)
问题是在继承时不使用__metaclass__
属性,这与您的预期相反。对于Bar
,不会调用“旧”元类。文档说明如何找到元类:
适当的元类由以下优先级确定 规则:
- 如果存在dict ['__ metaclass__'],则使用它。
- 否则,如果至少有一个基类,则使用其元类(这首先查找__class__属性,如果没有找到, 使用其类型)。
- 否则,如果存在名为__metaclass__的全局变量,则使用它。
- 否则,使用旧式的经典元类(types.ClassType)。
因此,您Bar
类中实际用作元类的内容位于父级__class__
属性中,而不是父级__metaclass__
属性中。
可以在this StackOverflow answer找到更多信息。
答案 1 :(得分:2)
类的元类信息在创建时使用(解析为类块,或动态地,通过显式调用元类)。它无法更改,因为元类通常会在类创建时进行更改 - 创建的类类型是metaclasse。它的__metaclass__
属性一旦创建就无关紧要了。
但是,可以创建给定类的副本,并使副本具有与原始类不同的元类。
在你的例子中,如果不是这样做:
Foo.__metaclass__ = MetaSub
Foo = Metasub("Foo", Foo.__bases__, dict(Foo.__dict__))
你将实现你的目标。新的Foo
用于所有效果与其前任相同,但具有不同的元类。
但是,以前存在的Foo
实例不会被视为新Foo
的实例 - 如果您需要,请最好使用其他名称创建Foo
副本
答案 2 :(得分:1)
子类使用其父级的 __元类__ 。
您的用例的解决方案是对父级的 __元类__ 进行编程,以使其对父级具有与其子类不同的行为。也许让它检查类字典中的类变量,并根据其值实现不同的行为 (这是 type 用于控制是否为实例提供字典的技术,具体取决于是否定义了 __ slots __ 。)