从父类覆盖类方法而不影响下游类
Posted
技术标签:
【中文标题】从父类覆盖类方法而不影响下游类【英文标题】:Overwrite class method from parent without affecting downstream classes 【发布时间】:2021-02-10 15:36:43 【问题描述】:我想覆盖一个类中的继承方法(参见下面的__init__
方法示例),同时让其子级仍然使用父母版本。
我知道我可以通过在GrandChild
类中重新定义__init__
方法或使用多重继承来实现所需的行为。但是,我的问题旨在通过仅更改 Child
类及其 __init__
实现来实现相同目的。
(实际用例是明显更复杂的“遗留代码”,每个级别都有几个类。因此,这个问题的动机是在各个类中实现所需的行为,而不必触及其他类的实现或继承结构)
如果这是不可能的,我也希望能对此做出解释。
class Parent:
def __init__(self, a,b):
self.a = a
self.b = b
def __str__(self):
return f"self.a, self.b"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a):
super().__init__(a, None)
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
# This throws a Type error right now since it calls the init method of class Child
grandchild = GrandChild("d", "e")
编辑:
如上所述,我知道我可以通过不同的方式实现所需的行为,例如更改类结构(如下所示)。然而,问题实际上更多是关于 python 是否允许仅对 Child
类进行更改。如果这在 python 中实际上是不可能的(不仅仅是不受欢迎的),那么解释为什么比提供替代实现来改变任何超出 Child
类的实现的替代实现来回答我的问题。
class ChildCommonFunctionality(Parent):
# Use this class for all common functionality originally provided by Child Class
pass
class Child(ChildCommonFunctionality):
# Use this class to override the init method
def __init__(self, a):
super().__init__(a, None)
class GrandChild(ChildCommonFunctionality):
# This Class should use the __init__ method of class Parent
pass
【问题讨论】:
虽然如果您愿意向后弯腰,可能会有解决方案,但这听起来只会引入不必要的混乱和令人惊讶的行为。你究竟需要改变什么行为,也许有不同的方式来实现。 看起来这个 Grandchild 类应该是 Parent 类的直接后代。是否真的有必要从 Child 继承某些东西?这不是避免提供答案,而是 OOP 应该是这样的,听起来你试图做一些 OOP 不适合的事情。此外,也许您正在尝试创建一个应该是 Child 的“兄弟”的新课程。 所以基本上我有一个现有的继承结构,其中一个类(又名Child
)为其所有子级提供通用功能,但也可以自己实例化。但是,它只需要其子级 (GrandChild
) 所需的构造函数参数的一个子集。目前所有类都使用***类 (Parent
) 的构造函数,这导致在中间实例化类时必须传递不必要的参数 (Child
)。我想更改此类的构造函数,同时尽量减少对现有类/继承所做的更改。
@madtyn :实际用例/继承树要复杂得多,每个级别上有几个类为其所有子类提供通用功能。如前所述,我正在尝试以上述方式(如果可行的话,这可能不是最干净的方式)来最小化我必须对现有类结构进行的更改。
嗨,克里斯,我知道您正在寻找“正常工作”的东西,但通常您想要的东西不仅有效,而且有意义并且对未来的修改具有强大的功能。创建一个只有超类的一部分属性的子类是没有意义的,因为两者之间存在is-a关系。子类应该添加属性和/或修改超类的行为。
【参考方案1】:
我找到了一种使用 _init_subclass 的方法来确保 Child
的所有子类都使用 Parent
的构造函数,而不是受 this post 启发的 Child
中定义的构造函数:
class Parent:
def __init__(self, a,b):
self.a = a
self.b = b
def __str__(self):
return f"self.a, self.b"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a):
super().__init__(a, None)
def __init_subclass__(cls):
cls.__init__ = super().__init__
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
尽管这有点 hacky,但它提供了实际绕过 Child
s 初始化方法的所需功能
【讨论】:
有趣!但是你确定super().__init_subclass__(**kwargs)
真的有必要吗?当被注释掉时,它也可以工作。
不确定是否真的有必要。从链接的帖子中复制的那种,将检查它是否也能正常工作。
不错。我读到这个 __init_subclass__
方法实际上是用于元类(类的类,如 type()),但我看到使用这种构造可以绕过 Child 的 init。
事实上super.__init_subclass
不是必需的,我相应地删除了它。【参考方案2】:
你可以这样做:
class Parent:
def __init__(self, a, b = None):
self.a = a
self.b = b
def __str__(self):
return f"self.a, self.b"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a, b = None):
super().__init__(a, b) # Or None instead of b... but that's not good when called by GrandChild
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
grandchild = GrandChild("d", "e")
编辑:您还可以用 GrandChild 中的强制参数替换可选参数:
class GrandChild(Child):
def __init__(self, a, b):
super().__init__(a, b)
【讨论】:
虽然这个答案会导致代码正常运行,但它确实改变了GrandChild
类的实现,使得第二个参数不再是强制性的。我想避免这种情况。
那么在这种情况下,您还可以删除可选参数并传递Child("c", None")
来构建一个子实例?恐怕这里没有完美的解决方案。在其他一些语言中,您可以直接调用层次结构的特定级别(例如超级超级),但我认为这在 Python 中是不可能的。
为Child
构造函数中的多余参数手动传递None
是我想要更改的当前行为,因为我发现它令人困惑。在像 Java 这样的另一种语言中,我可能会使用方法重载来解决这个问题,但不幸的是,这在 python 中似乎不起作用。有一个 overload
装饰器,但遗憾的是它不允许具有不同数量参数的签名。
我编辑了我的帖子,为您在之前评论中指出的问题提出解决方案
@dspr 虽然你的代码没有报错,但 GrandChild 的 b 属性现在是 None 而不是 "e"。【参考方案3】:
这段代码可以解决问题,在@dspr 的建议中添加几行:
class Parent:
def __init__(self, a, b = None):
self.a = a
self.b = b
def __str__(self):
return f"self.a, self.b"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a, b = None):
if type(self) == Child:
if b is not None:
raise ValueError(
"Second argument is not allowed for direct use in Child class")
super().__init__(a, None) #Or (a, b) if you trust b to be None as it is here
else:
super().__init__(a, b)
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
print(child.b) # None
grandchild = GrandChild("d", "e")
print(grandchild.b) # e
child = Child("f", "g")
print(child.b) # ValueError
【讨论】:
这似乎是我所见过的最好的解决方案,因为它在仅更改Child
类的给定约束内实现了所需的行为。然而这显然仍然导致GrandChild
类使用Child
的__init__ 方法而不是Parent
。我仍然很好奇是否有任何方法可以避免这种情况(例如,一些装饰器或元类)或者这实际上是不可能的,所以我会稍微接受你的回答。
另外理想情况下,我希望 Child
类的 init 方法根本不接受第二个参数。
嗨@Chris,我认为GrandChild 类的初始化不可能跳过Child 类的初始化。为了禁止将参数传递给 b,您可以在 Child 类中执行的操作是在 if 块中添加几行。我会将它们添加到我的代码中。
我认为我实际上找到了绕过它的方法(请参阅我自己对这个问题的回答)以上是关于从父类覆盖类方法而不影响下游类的主要内容,如果未能解决你的问题,请参考以下文章