动态更改遗产树中的给定类并将其替换为另一个并初始化它

Posted

技术标签:

【中文标题】动态更改遗产树中的给定类并将其替换为另一个并初始化它【英文标题】:Dynamically change a given class in the heritage tree and replace it with another one and initialize it 【发布时间】:2019-09-26 12:54:05 【问题描述】:

我目前正在研究图书馆。

我想编写一个包装器,通过一个新类(假设为WindowedMean)修改继承树中的一个类(示例中为Mean),我想初始化这个类(例如 k=10)。

Mean 类可以位于遗产树中的任何位置,这只是一个示例。

This link shows the example

我知道这是不可取的。 你有一个优雅的方法来做到这一点吗?

我想像这样使用包装器:

metric = Wrapper(MyClass, k=10)

【问题讨论】:

我不清楚你到底想要完成什么,你能详细说明一下吗?期望的行为究竟是什么? 我有一个类可以增量计算Accuracy,它继承自 Mean 类。我的包装器的作用是通过将Mean 替换为RollingMean 来更改Accuracy 类的继承。最后两个类具有完全相同的方法。通过这种方式,我可以轻松地使用滚动窗口计算指标。你更清楚了吗? 是的 - 最后的评论实际上是有道理的。问题的文本,实际上,并没有那么多。 【参考方案1】:

更新

虽然下面的解决方案将完全按照您描述的方式工作,但我注意到使用多重继承,您所要求的可以自然发生。

只继承你要修改的类,使用正常的继承机制覆盖继承的类行为。这意味着,如果需要,设置k=10 类属性,并将超级调用硬编码到Mean 的父级而不是使用super

然后,只需创建 MyClass 的新子代,并将 Mean 的覆盖子代添加到继承树中。请注意,MyClass 的这个子类在其主体中不需要单个语句,并且其行为与 MyClass 完全相同,除了修改后的 Mean 现在位于 mro 中的适当位置。

直接在解释器中(我不得不求助于exec 才能在一行中输入所有类层次结构)

In [623]: exec("class GreatGrandParent1: pass\nclass GreatGrandParent2: pass\nclass GrandParent1(GreatGrandParent1, Gre
     ...: atGrandParent2): pass\nclass Mean: pass\nclass Parent1(GrandParent1, Mean): pass\nclass Parent2: pass\nclass 
     ...: MyClass(Parent1, Parent2): pass")                                                                            

In [624]: MyClass.__mro__                                                                                              
Out[624]: 
(__main__.MyClass,
 __main__.Parent1,
 __main__.GrandParent1,
 __main__.GreatGrandParent1,
 __main__.GreatGrandParent2,
 __main__.Mean,
 __main__.Parent2,
 object)

In [625]: class MeanMod(Mean): 
     ...:     k = 10 
     ...:                                                                                                              

In [626]: class MyClassMod(MyClass, MeanMod): pass                                                                     

In [627]: MyClassMod.__mro__                                                                                           
Out[627]: 
(__main__.MyClassMod,
 __main__.MyClass,
 __main__.Parent1,
 __main__.GrandParent1,
 __main__.GreatGrandParent1,
 __main__.GreatGrandParent2,
 __main__.MeanMod,
 __main__.Mean,
 __main__.Parent2,
 object)

请注意,在这种情况下,这不会直接起作用:如果在您的示例中,Mean 中的 super 调用应该调用 Parent2 中的方法。在这种情况下,要么求助于原始解决方案,要么使用一些巧妙的__mro__ 操作来跳过Mean 中的方法。

原始答案

看起来这可以与类装饰器一起使用(也可以与您想要的这种“包装器”语法一起使用)。

确实存在一些极端情况,并且可能会出错 - 但如果你的树表现得有些好,我们必须递归地采用 all 你想要包装的类的基础,并制作在这些基础上进行替换 - 我们不能简单地取最后一个基础,展开其 __mro__ 中的所有祖先类,然后在那里替换所需的类:遗产线会在下面被打破。

也就是说,假设你有类A, B(A), C(B), D(C),并且你想要一个D 的克隆D2,将B 替换为B2(A) - D.__bases__CD.__mro__(D, C, B, A, object) 如果我们尝试创建一个新的 D2 强制 __mro__ 成为 (D, C, B2, A, object)C 类会中断,因为它不会再看到 B。 (并且此答案的先前版本中的代码会将 B 和 B2 都留在继承行中,从而导致进一步的经纪)。 下面的解决方案不仅重新创建了一个新的B 类,还重新创建了一个新的C

请注意,如果B2 本身不会从A 继承,在同一个示例中,A 本身将从__mro__ 中删除,因为新的被替换的 B2 不需要它。如果D 有任何依赖于A 的功能,它将中断。这不容易解决,但是为了设置检查机制以确保被替换的类包含被替换类的相同祖先,否则会引发错误 - 这很容易做到。但是弄清楚如何挑选和包含“现在失踪”的祖先并不容易,因为根本不可能知道它们是否需要,只是开始。

至于配置部分,如果没有一些示例说明您的 RollingMean 类是如何“配置”的,很难给出一个适当的具体示例 - 但是让我们创建它的一个子类,使用传递的参数更新它的 dict - 那应该做任何需要的配置。

from types import new_class

def norm_name(config):
    return "_" + "__".join(f"key_value" for key, value in config.items())

def ancestor_replace(oldclass: type, newclass: type, config: dict):
    substitutions = 
    configured_newclass = type(newclass.__name__ + norm_name(config), (newclass,), config)
    def replaced_factory(cls):
        if cls in substitutions:
            return substitutions[cls]
        bases = cls.__bases__
        new_bases = []
        for base in bases:
            if base is oldclass:
                new_bases.append(configured_newclass)
            else:
                new_bases.append(replaced_factory(base))
        if new_bases != bases:
            new_cls = new_class(cls.__name__, tuple(new_bases), exec_body=lambda ns: ns.update(cls.__dict__.copy()))
            substitutions[cls] = new_cls
            return new_cls
        return cls

    return replaced_factory


 # canbe used as:

 #MyNewClass = ancestor_replace(Mean, RollingMean, "ks": 10)(MyClass)

我会注意派生正确的元类 - 如果您的继承树中没有任何类使用 type 以外的元类,(通常 ABC 或 ORM 模型具有不同的元类),您可以使用 type 而不是types.new_class 在通话中。

最后,一个在交互式提示中使用它的例子:

In [152]: class A: 
     ...:     pass 
     ...:  
     ...: class B(A): 
     ...:     pass 
     ...:  
     ...: class B2(A): 
     ...:     pass 
     ...:  
     ...: class C(B): 
     ...:     pass 
     ...:  
     ...: class D(B): 
     ...:     pass 
     ...:  
     ...: class E(D): 
     ...:     pass 
     ...:                                                                                                                         

In [153]: E2 = ancestor_replace(B, B2, "k": 20)(E)                                                                              

In [154]: E.__mro__                                                                                                               
Out[154]: (__main__.E, __main__.D, __main__.B, __main__.A, object)

In [155]: E2.__mro__                                                                                                              
Out[155]: 
(__main__.E_modified,
 __main__.D_modified,
 __main__.B2_k_20,
 __main__.B2,
 __main__.A,
 object)

In [156]: E2.k                                                                                                                    
Out[156]: 20

【讨论】:

【参考方案2】:

也许是这样的?

class Parent1(object):
    def __init__(self):
        super(Parent1, self).__init__()

    def speak(self):
        print('Iam parent 1')

class Parent2(object):
    def __init__(self):
        super(Parent2, self).__init__()

    def speak(self):
        print('Iam parent 2')

class Child():
    """docstring for Child."""
    def __init__(self):
        pass

    def child_method(self):
        print('child method')

def make_child(inhert):
    child = Child()

    class ChildInhert(Child, inhert):

        def __init__(self):
            inhert.__init__(self)

    return ChildInhert()

if __name__ == '__main__':
    child = make_child(Parent1)
    child.speak()
    child.child_method()

【讨论】:

以上是关于动态更改遗产树中的给定类并将其替换为另一个并初始化它的主要内容,如果未能解决你的问题,请参考以下文章

读取文本文件中的内容并使用 VBS 将其替换为另一个文本文件中的指定文本

Jaxb:如何在解组时替换给定对象树中的类/绑定

如何从 UINavigationController 弹出视图并在一次操作中将其替换为另一个视图?

存储动态字符串

我如何过滤掉 NaN 值并将其替换为另一个值

在 App Store 中将一个应用替换为另一个