为什么我不能更改类的__metaclass__属性?

时间:2012-01-15 02:06:36

标签: python 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 个答案:

答案 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 __ 。)