我发现自己处于一种不寻常的情况下,需要在运行时更改类的MRO。
编码:
class A(object): def __init__(self): print self.__class__ print "__init__ A" self.hello() def hello(self): print "A hello" class B(A): def __init__(self): super(B, self).__init__() print "__init__ B" self.msg_str = "B" self.hello() def hello(self): print "%s hello" % self.msg_str a = A() b = B()
可以预料,由于__init__A的方法(从B调用)会调用B hello,而B在属性存在之前尝试访问该属性,因此这将失败。
__init__
hello
问题在于,我无法进行更改:
我确实通过在运行时更改MRO来从概念上解决了这一问题。简而言之,在B期间__init__,但在调用super之前__init__,将更改MRO,以便首先搜索A的方法,从而调用Ahello而不是B(因此失败)。
问题是MRO是只读的(在类运行时)。
还有另一种方法可以实现吗?或者可能是一个完全不同的解决方案(仍然遵守上述约束)?
如果您不受问题中提到的约束的约束,则建议使用其他提供的答案。否则,我们需要踏入mro hack和metaclass领域。
一番阅读之后,我发现您可以使用元类来更改类的mro。
但是,这是在类创建时,而不是在对象创建时。轻微修改是必要的。
元类提供了mro我们重载的方法,该方法在类创建期间__new__调用(元类的调用)以产生__mro__属性。
mro
__new__
__mro__
该__mro__属性不是普通属性,因为:
但是,mro更改类的基数时,似乎重新计算了(使用该方法)。这构成了黑客的基础。
简单来说:
B
change_mro_meta
change_mro
至于提到,修改类的MRO,而在它__init__不是线程安全的。
以下内容可能会打扰一些观众。建议观看者谨慎。
hack:
class change_mro_meta(type): def __new__(cls, cls_name, cls_bases, cls_dict): out_cls = super(change_mro_meta, cls).__new__(cls, cls_name, cls_bases, cls_dict) out_cls.change_mro = False out_cls.hack_mro = classmethod(cls.hack_mro) out_cls.fix_mro = classmethod(cls.fix_mro) out_cls.recalc_mro = classmethod(cls.recalc_mro) return out_cls @staticmethod def hack_mro(cls): cls.change_mro = True cls.recalc_mro() @staticmethod def fix_mro(cls): cls.change_mro = False cls.recalc_mro() @staticmethod def recalc_mro(cls): # Changing a class' base causes __mro__ recalculation cls.__bases__ = cls.__bases__ + tuple() def mro(cls): default_mro = super(change_mro_meta, cls).mro() if hasattr(cls, "change_mro") and cls.change_mro: return default_mro[1:2] + default_mro else: return default_mro class A(object): def __init__(self): print "__init__ A" self.hello() def hello(self): print "A hello" class B(A): __metaclass__ = change_mro_meta def __init__(self): self.hack_mro() super(B, self).__init__() self.fix_mro() print "__init__ B" self.msg_str = "B" self.hello() def hello(self): print "%s hello" % self.msg_str a = A() b = B()
一些注意事项:
的hack_mro,fix_mro而recalc_mro方法是staticmethods到元类,但classmethods到类。它做到了这一点,而不是多重继承,因为我想将mro代码分组在一起。
hack_mro
fix_mro
recalc_mro
该mro方法本身通常返回默认值。在破解条件下,它将默认mro的第二个元素(直接父类)附加到mro,从而使父类首先在子类之前看到自己的方法。
我不确定此黑客的可移植性。它已在Windows 7 64bit上运行的64bit CPython 2.7.3上进行了测试。
不用担心,我敢肯定这不会在某个地方的生产代码中结束。