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

标签 python class oop metaclass method-resolution-order

我目前在图书馆工作。

我想编写一个包装器,通过一个新类(假设 Mean)在继承树中修改一个类(示例中为 WindowedMean),我想 初始化 此类(例如 k=10 )。
Mean类可以位于遗产树中的任何位置,这只是一个示例。

This link shows the example

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

我想像这样使用包装器:

metric = Wrapper(MyClass, k=10)

最佳答案

更新

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

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

然后,只需创建 MyClass 的新 child 并添加 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)

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

原答案

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

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

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

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

至于配置部分,没有你的RollingMean的例子。类是“配置的”,很难给出一个合适的具体例子——但让我们创建一个它的子类,用传递的参数更新它的字典——这应该适用于任何需要的配置。
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

关于python - 动态更改遗产树中的给定类并将其替换为另一个并初始化它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56046674/

相关文章:

c# - 继承类作为引用

java - 将 Java 对象的类型/类名称声明为字符串

oop - 更重要的是代码的可测试性还是遵守 OOP 原则?

python - googletrans 停止工作,出现错误 'NoneType' 对象没有属性 'group'

python ctypes 与 namedtuple

c++ - 未定义的类引用?

java - 在JAVA中实例化一个抽象类?

javascript - JavaScript 中的继承导致 Uncaught TypeError

python - 如何使用 django-admin.py 运行特定测试?

python - 获取一个元素列表,插入非唯一元素,然后返回包含三个元素的列表的列表。