在运行时更改python mro [英] Change python mro at runtime

查看:203
本文介绍了在运行时更改python mro的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现自己处于一个异常情况,我需要在运行时更改类的MRO。



代码:

  class A(object):
def __init __(self):
print self .__ class__
print__init__ A
self.hello()

def hello(self):
printA hello

class B(A):
def __init __(self):
super(B,self).__ init __()
print__init__ B
self.msg_str =B
self.hello b
$ b def hello(self):
print%s hello%self.msg_str

a = A()
b = B b

由于 __ init __ 方法A(当从B调用时)调用B的 hello ,尝试在属性存在之前访问该属性。



问题是我对我可以做的改变有限制:




  • B必须子类A

  • A不能改变

  • A和B都需要一个hello方法

  • B在调用super之前无法初始化其他属性 __init __



通过在运行时更改MRO,我在概念上解决了这个问题。简而言之,在B __ init __ 期间,但在调用super __ init __ 之前,MRO将被更改,方法第一,因此调用A的 hello 而不是B的(因此失败)。



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



有另一种方法来实现吗?

解决方案

其他提供的答案是可取的,如果你不是受问题中提到的约束的约束。



经过一番阅读,我发现你可以使用元类更改类的mro



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



元类提供了 mro 方法,我们重载, (元类 __ new __ 调用)以产生 __ mro __ 属性。



__ mro __ 属性不是正常属性,因为:


  1. 只读

  2. 定义 BEFORE 元类 __ new __ 调用

然而,当一个类的基数为'base'时,它似乎被重新计算(使用 mro 方法)改变。



简要说明:




  • 子类( change_mro_meta )创建一个元类( B 此元类提供:

    • 重载的mro方法

    • 更改 __ mro __ 属性

    • 用于控制mro行为的类属性( change_mro




作为提到,修改类的mro,而在 __ init __ 中不是线程安全的。



以下可能会扰乱一些观众。建议观看者酌情处理。



黑客:

  class change_mro_meta类型):
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):
#改变类的基础导致__mro__重新计算
cls .__ bases__ = cls .__ bases__ + tuple()

def mro(cls):
default_mro = super(change_mro_meta,cls)。 mro()
如果hasattr(cls,change_mro)和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 ):
printA 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 )

一些注释:



code> hack_mro fix_mro recalc_mro 方法是元类的静态方法,类方法到类。



mro 方法不是多重继承,而是将mro代码分组在一起。本身通常返回默认值。在hack条件下,它将默认mro(立即父类)的第二个元素附加到mro,从而使父类在子类之前首先查看自己的方法。



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



不要担心,我确信这不会在生产代码中结束某处


I've found myself in an unusual situation where I need to change the MRO of a class at runtime.

The code:

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

As to be expected, this fails as the __init__ method of A (when called from B) calls B's hello which attempts to access an attribute before it exists.

The issue is that I'm constrained in the changes I can make:

  • B must subclass A
  • A cannot be changed
  • Both A and B require a hello method
  • B cannot initialise other attributes before calling the super __init__

I did solve this conceptually, by changing the MRO at runtime. In brief, during B's __init__, but before calling super __init__, the MRO would be changed so that A would be searched for methods first, thereby calling A's hello instead of B's (and therefore failing).

The issue is that MRO is read only (at class runtime).

Is there another way to implement this ? Or possibly a different solution altogether (that still respects the aforementioned constraints) ?

解决方案

The other provided answers are advisable if you are not bound by the constraints mentioned in the question. Otherwise, we need to take a journey into mro hacks and metaclass land.

After some reading, I discovered you can change the mro of a class, using a metaclass.

This however, is at class creation time, not at object creation time. Slight modification is necessary.

The metaclass provides the mro method, which we overload, that is called during class creation (the metaclass' __new__ call) to produce the __mro__ attribute.

The __mro__ attribute is not a normal attribute, in that:

  1. It is read only
  2. It is defined BEFORE the metaclass' __new__ call

However, it appears to be recalculated (using the mro method) when a class' base is changed. This forms the basis of the hack.

In brief:

  • The subclass (B) is created using a metaclass (change_mro_meta). This metaclass provides:
    • An overloaded mro method
    • Class methods to change the __mro__ attribute
    • A class attribute (change_mro) to control the mro behaviour

As mentioned, modifying the mro of a class while in its __init__ is not thread safe.

The following may disturb some viewers. Viewer discretion is advised.

The 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()

Some notes:

The hack_mro, fix_mro and recalc_mro methods are staticmethods to the metaclass but classmethods to the class. It did this, instead of multiple inheritance, because I wanted to group the mro code together.

The mro method itself returns the default ordinarily. Under the hack condition, it appends the second element of the default mro (the immediate parent class) to the mro, thereby causing the parent class to see its own methods first before the subclass'.

I'm unsure of the portability of this hack. Its been tested on 64bit CPython 2.7.3 running on Windows 7 64bit.

Don't worry, I'm sure this won't end up in production code somewhere.

这篇关于在运行时更改python mro的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆