【问题标题】:Why can't I change the __metaclass__ attribute of a class?为什么我不能更改类的 __metaclass__ 属性?
【发布时间】:2012-02-10 15:04:16
【问题描述】:

我有一个奇怪且不寻常的元类用例,我想在定义基类后更改其__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__))

【问题讨论】:

    标签: python metaclass


    【解决方案1】:

    问题是__metaclass__ 属性在继承时未使用,这与您的预期相反。 Bar 也没有调用“旧”元类。文档说明了如何找到元类:

    适当的元类由以下优先级确定 规则:

    • 如果 dict['__metaclass__'] 存在,则使用它。
    • 否则,如果至少有一个基类,则使用其元类(这会首先查找 __class__ 属性,如果未找到, 使用它的类型)。
    • 否则,如果存在名为 __metaclass__ 的全局变量,则使用它。
    • 否则,将使用旧式经典元类 (types.ClassType)。

    因此,在您的 Bar 类中实际用作元类的是在父级的 __class__ 属性中,而不是在父级的 __metaclass__ 属性中。

    更多信息请访问this StackOverflow answer

    【讨论】:

      【解决方案2】:

      一个类的元类信息在它被创建的那一刻被使用(或者被解析为一个类块,或者动态地,通过对元类的显式调用)。它无法更改,因为元类通常会在类创建时进行更改——创建的类类型是元类。它的__metaclass__ 属性一旦创建就无关紧要了。

      但是,可以创建给定类的副本,并让该副本具有与原始类不同的metclass。

      在你的例子中,如果不是这样做:

      Foo.__metaclass__ = MetaSub

      你会的:

      Foo = Metasub("Foo", Foo.__bases__, dict(Foo.__dict__))

      您将实现您的预​​期。新的Foo 适用于所有效果与其前身相同,但具有不同的元类。

      但是,以前存在的 Foo 实例不会被视为新 Foo 的实例 - 如果需要,最好使用不同的名称创建 Foo 副本。

      【讨论】:

      • 感谢您的提示,这并没有完全符合我的要求,因为我仍然希望 Foo 的子类拥有元类,但我已经编辑了我的问题以包含解决方案根据您的建议,我需要什么。
      【解决方案3】:

      子类使用其父类的 __metaclass__

      您的用例的解决方案是对父类的 __metaclass__ 进行编程,使其对父类的行为与其子类不同。也许让它检查类字典中的类变量并根据其值实现不同的行为 (这是 type 用来根据是否定义了 __slots__ 来控制是否为实例提供字典的技术。

      【讨论】:

      • 在我的实际示例中,父类来自第三方模块,我想从该模块扩展一个类,但有我自己的元类,它覆盖了第三方的一些行为-派对模块的元类。否则我肯定会按照你的建议去做。
      • @EliCourtwright 也许您可以推出自己的类来委托给第三方模块,而不是从它继承。这应该使您可以完全控制类创建过程,同时最大限度地重用代码。
      猜你喜欢
      • 1970-01-01
      • 2017-05-27
      • 1970-01-01
      • 1970-01-01
      • 2015-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多