在运行时更改 python mro



我发现自己处于一种不寻常的情况,我需要在运行时更改类的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 在调用超级__init__之前无法初始化其他属性

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

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

有没有另一种方法来实现这一点?或者可能完全不同的解决方案(仍然尊重上述约束)?

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

经过一些阅读,我发现您可以使用元类更改类的 mro。

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

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

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

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

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

简述:

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

如前所述,在类的__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,从而使父类在子类之前首先看到自己的方法。

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

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

可能有更大的解决方案,但一个简单的选择是防御性地编写 B 类。例如:

class B(A):
def __init__(self):
super(B, self).__init__()
print "__init__ B"
self.msg_str = "B"
self.hello()
def hello(self):
if not hasattr(self, 'msg_str'):
A.hello(self)
return
print "%s hello" % self.msg_str

具有正则表达式功能的优秀编辑器可以自动插入适当的if not hasattr(self, 'some_flag'):...行作为 B 中任何方法的第一行。

对于您的特定示例,一种解决方案是给 B 一个class属性来保存默认消息:

class B(A):
msg_str = "default msg"
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

通常这会导致混乱,但在这种情况下可能会有所帮助。 如果在设置实例的msg_str之前调用B.hello,它将读取 1 类。 设置实例msg_str后,它将隐藏类 1,以便将来访问self.msg_str将看到特定于实例的类。

我不太明白为什么在调用超类之前不能设置属性__init__. 根据实际情况,可能还有其他解决方案。

我的解决方案是请求原谅:

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):
try:
print "%s hello" % self.msg_str
except AttributeError:
pass  # or whatever else you want
a = A()
b = B()

或者,如果您不想重构从init调用的方法:

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):
try:
super(B, self).__init__()
except AttributeError:
pass  # or whatever else you want
print "__init__ B"
self.msg_str = "B"
self.hello()
def hello(self):
print "%s hello" % self.msg_str
a = A()
b = B()

我想指出一个解决方案,该解决方案非常特定于您在问题中提供的示例,因此不太可能有所帮助。(但万一它确实有帮助...

你可以绕过hello的多态性,方法是将其定义为类成员,而不是方法。

class B(A):
def __init__(self):
super(B, self).__init__()
print "__init__ B"
self.msg_str = "B"
self.hello = lambda: print "%s hello" % self.msg_str
self.hello()

(A保持不变)。

如果出现以下情况,此解决方案将中断:

  • 您的子类B,需要覆盖子类中的hello
  • msg_str在运行__init__后修改
  • 可能还有其他几个 例。。。

我找到了一种更改对象的类或重写其 mro 的方法。

最简单的方法是使用type函数构建一个新类:

def upgrade_class(obj, old_class, new_class):
if obj.__class__ is old_class:
obj.__class__ = new_class
else:
mro = obj.__class__.mro()
def replace(cls):
if cls is old_class:
return new_class
else:
return cls
bases = tuple(map(replace, mro[1:]))
old_base_class = obj.__class__
new_class = type(old_base_class.__name__, bases, dict(old_base_class.__dict__))
obj.__class__ = new_class

我不知道它是否与特定问题相关,但在我看来,像这样动态更改 MRO 在并发程序中可能会有风险,并且如果这些对象中的任何一个被递归创建,肯定会有问题。

我遇到了一个非基于 MRO 的解决方案,具体取决于此代码可能遇到的错误的性质。(请记住,这是迟来的。也许其他人会想要一个不同的答案。

基本上,B 上的每个 hello() 方法都将包装在一个装饰器中。类似的东西

class deferring(object):
def __init__(self, function):
self.function = function
def __get__(self, instance, owner):
# Return an unbound method, or whatever, when called from B.
if instance is None:
return self.function.__get__(None, owner)
else:
# Indicate that an instance is ready via a flag.
# May need to tweak this based on the exact problem.
if hasattr(instance, '_set_up'):
return self.function.__get__(instance, owner)
else:
# Walk the mro manually.
for cls in owner.__mro__:
# Crazy inefficient. Possible to mitigate, but risky.
for name, attr in vars(cls).items():
if attr is self:
break
else:
continue
return getattr(super(cls, instance), name)
else:
raise TypeError

如果你不想走描述符路线,也可以做一些类似的事情

def deferring(function):
def wrapped(self, *args, **kwargs):
if hasattr(self, '_set_up'):
return function(self, *args, **kwargs)
else:
for cls in type(self).__mro__:
for name, attr in vars(cls).items():
if attr is function:
break
else:
continue
return getattr(super(cls, self), name)(*args, **kwargs)
else:
raise TypeError
return wrapped

super(B, self).__init__()之前调用self.msg_str = "B"

这不是好的做法,可能有更好的方法来解决您的用例。但一般来说,您可以在运行时修改实例__class__,这将修改实例(而不是类)的 mro。

例如:

class A:
def f(self):
print('a')
class B:
def f(self):
print('b')
b = B()
b.f()  # b
b.__class__ = A
b.f()  # a
assert type(b) is A

使用您的用例,它看起来像:

class A:
def __init__(self):
self.hello()
def hello(self):
print("A hello")
class B(A):
def __init__(self):
parent_init = super().__init__
# While A.__init__ is called, inheritance is removed
self.__class__ = A
parent_init()
self.__class__ = B  # Restore inheritance
self.msg_str = "B"
def hello(self):
print("%s hello" % self.msg_str)
a = A()  # A hello
b = B()  # A hello  (called in A.__init__)
b.hello()  # B hello

相关内容

  • 没有找到相关文章

最新更新