问题描述
我发现自己处于一个异常情况,我需要在运行时更改类的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
方法,我们重载, (元类 __ new __
调用)以产生 __ mro __
属性。
__ mro __
属性不是正常属性,因为:
- 只读
- 定义 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:
- It is read only
- 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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!