我目前在图书馆工作。
我想编写一个包装器,通过一个新类(假设 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)
,而你想要一个克隆 D2
的 D
, 替换 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/