从父类覆盖类方法而不影响下游类

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,但它提供了实际绕过 Childs 初始化方法的所需功能

【讨论】:

有趣!但是你确定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 块中添加几行。我会将它们添加到我的代码中。 我认为我实际上找到了绕过它的方法(请参阅我自己对这个问题的回答)

以上是关于从父类覆盖类方法而不影响下游类的主要内容,如果未能解决你的问题,请参考以下文章

java子类从父类继承某个属性,怎么添加特定的注解不影响父类

抽象类

java中的重载和重写

Java复习类的重用

反射:从父类获取静态方法

JAVA 继承