python - 在运行时更改 python mro

标签 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()

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

问题是我在可以进行的更改方面受到限制:

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

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

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

还有其他方法可以实现吗?或者可能完全不同的解决方案(仍然遵守上述限制)?

最佳答案

如果您不受问题中提到的限制的约束,建议使用其他提供的答案。否则,我们需要进入 mro hack 和元类领域。

经过一些阅读,我发现你可以 change the mro类的,使用元类。

然而,这是在类创建时,而不是在对象创建时。需要稍作修改。

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

__mro__ 属性不是普通属性,因为:

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

但是,当类的基数发生变化时,它似乎会重新计算(使用 mro 方法)。这构成了黑客攻击的基础。

简而言之:

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

作为mentioned ,在类的 __init__ 中修改类的 mro 不是线程安全的。

以下内容可能会打扰到部分观众。建议观众自行决定。

黑客:

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_mrofix_mrorecalc_mro 方法是元类的静态方法,但类的类方法。它这样做,而不是多重继承,因为我想将 mro 代码组合在一起。

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

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

别担心,我确信这不会在某个地方出现在生产代码中。

关于python - 在运行时更改 python mro,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20822850/

相关文章:

python - 迭代到 pandas Dataframe 列时如何删除行?

python - 管理中的动态选择选项

python - 如何使用python将excel表格粘贴到word中

c++ - 如何在C++中的赋值中命名类标识符?

r - 如何使通用 R 函数继承其输入的类?

python - Python 的 MRO、C3 线性化是否深度优先?根据经验,它没有

python - 如何从用于弹性插入的python代码改进parallel_bulk?

c++ - 将现有的类结构移植到智能指针

python - 如果将基类的可选功能存储在二级类中,二级类是否应该继承基类?

raku - Perl 6 对象如何找到可能在父类或角色中的多方法?