在运行时更改python mro

dil*_*ert 7 python class method-resolution-order

我发现自己处于一种不寻常的情况,我需要在运行时更改类的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()
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这失败了,因为__init__A 的方法(当从B调用时)调用B hello,它试图在属性存在之前访问它.

问题在于我可以做出的改变受到限制:

  • B必须是A的子类
  • A不能改变
  • A和B都需要hello方法
  • 在调用super之前,B无法初始化其他属性 __init__

我确实通过在运行时更改MRO来解决这个问题.简而言之,在B期间__init__,但在调用super之前__init__,MRO将被更改,以便首先搜索A的方法,从而调用A hello而不是B(因此失败).

问题是MRO是只读的(在类运行时).

还有其他方法可以实现吗?或者可能完全不同的解决方案(仍然尊重上述约束)?

dil*_*ert 11

如果您不受问题中提到的约束条件的约束,则建议使用其他答案.否则,我们需要前往mro hacks和metaclass land.

经过一些阅读,我发现你可以使用元类来改变的mro.

然而,这是在类创建时,而不是在对象创建时.稍微修改是必要的.

元类提供了mro我们重载的方法,在类创建(元类' __new__调用)期间调用该方法以生成__mro__属性.

__mro__属性不是普通属性,其中:

  1. 它是只读的
  2. 它是元类' __new__调用之前定义的

但是,mro当更改类的基础时,似乎会重新计算(使用该方法).这构成了黑客的基础.

简单来说:

  • 子类(B)是使用元类(change_mro_meta)创建的.这个元类提供:
    • 一个重载的mro方法
    • 用于更改__mro__属性的类方法
    • change_mro用于控制mro行为的class attribute()

正如提到的,修改的一类,而的MRO它__init__不是线程安全的.

以下可能会打扰一些观众.建议观众自行决定.

黑客:

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()
Run Code Online (Sandbox Code Playgroud)

一些说明:

hack_mro,fix_mrorecalc_mro方法是staticmethods到元类,但classmethods到类.它这样做,而不是多重继承,因为我想将mro代码组合在一起.

mro方法本身通常返回默认值.在hack条件下,它将默认mro的第二个元素(直接父类)附加到mro,从而使父类在子类之前首先看到它自己的方法.

我不确定这个黑客的可移植性.它已经在Windows 7 64bit上运行的64位CPython 2.7.3上进行了测试.

别担心,我确信这不会在某个地方的生产代码中结束.

  • +,我从中学到了很多关于代码加密的知识。 (2认同)