动态更改继承树中给定的 class 并将其替换为另一个并初始化它

Dynamically change a given class in the heritage tree and replace it with another one and initialize it

我目前正在图书馆工作。

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

Mean class 可以在遗产树中的任何地方这只是一个例子。

This link shows the example

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

我想像这样使用包装器:

metric = Wrapper(MyClass, k=10)

也许是这样的?

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

更新

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

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

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

直接在解释器中(我不得不诉诸 exec 才能在一行中键入所有 class 层次结构)

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.

中的方法

原回答

看起来这可以与 class-decorator 一起使用(也可以与您想要的 "wrapper" 语法一起使用)。

确实有一些角落情况,以及可能出错的地方 - 但如果你的树表现得有点好,我们必须递归地采用 all class 的基础] 你想包装,并在这些基础中进行替换 - 我们不能简单地采用最终基础,展开其 __mro__ 中的所有祖先 classes 并替换所需的 class那里:遗产线将在它下面被打破。

也就是说,假设您有 classes 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) , class C 会中断,因为它不会再看到 B 了。 (并且此答案的先前版本中的代码会将 B 和 B2 都留在继承行中,从而导致进一步的中介)。 下面的解决方案不仅重新创建了一个新的 B class,而且重新创建了一个新的 C.

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

至于配置部分,没有一些例子说明你的 RollingMean class 是如何 "configured" 很难给出一个合适的具体例子 - 但让我们做一个子class,用传递的参数更新它的字典——这应该适用于任何需要的配置。

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)

我在那里仔细推导了正确的元class - 如果您的继承树中没有 class 使用 type 以外的元class,(通常 ABC 或 ORM 模型具有不同的元 classes),您可以在调用中使用 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