2012-01-15 78 views
6

我有一个奇怪的和不常见的用例,我想在基类的__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__)) 

回答

2

类的元类信息在创建时使用(或者作为类块进行解析,或者对元类进行显式调用时动态地使用)。它不能改变,因为元类通常会在类创建时进行更改 - 创建的类类型是元类型。它的__metaclass__属性一旦创建就无关紧要。

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

在你的榜样,如果不是做:

Foo.__metaclass__ = MetaSub

你这样做:

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

你会实现你的目的是什么。新的Foo适用于所有与其前任相同的效果,但具有不同的元类。

然而,Foo以前存在的情况下,将不考虑新的Foo的实例 - 如果你需要,你最好使用不同的名称创建Foo复印件。

+0

感谢您的提示,这并没有做到我想要的,因为我仍然希望'Foo'的子类具有元类,但是我已经编辑了我的问题以包含根据您的需要做了我需要的解决方案建议。 – 2012-01-15 06:32:34

3

的问题是不使用__metaclass__属性时继承,违背你所期望的。对于Bar,“旧”元类别不会被调用。该文件说以下有关元类是如何发现:

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

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

那么在你Bar类实际上作为元类的父的__class__属性,而不是在父母的__metaclass__属性被发现。

更多信息可在this StackOverflow answer上找到。

1

子类使用父母的__metaclass__

您的使用案例的解决方案是对父级的__metaclass__进行编程,以便它对于父级的行为不同于其子类。也许让它检查类字典中的类变量,并根据其值执行不同的行为 (这是技术类型用于根据是否定义是否定义实例来控制实例是否有字典)。

+0

在我的实际示例中,父类来自第三方模块,我想从该模块扩展一个类,但拥有自己的元类,它覆盖了第三方模块元类的一些行为。否则,我一定会做你的建议。 – 2012-01-15 04:53:33

+0

@EliCourtwright也许你可以推出自己的类,委托给第三方模块,而不是继承它。这应该让您可以完全控制类创建过程,同时最大限度地实现代码重用。 – 2012-01-15 05:01:02

相关问题