动态更改继承树中给定的 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__
是 C
。 D.__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
我目前正在图书馆工作。
我想写一个包装器,通过新的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__
是 C
。 D.__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