0

I would like to overwrite an inherited method in a class (see below example for __init__ method) while letting its children still use the Parents version.

I know that I could achieve the desired behaviour redefining the __init__ method in the GrandChild class or using multiple inheritance. However my question aims at a way to achieve the same with changes only to the Child class and its __init__ implementation.

(The actual use case is significantly more complex 'legacy code' with several classes on each level. The motivation of this question is therefore to achieve the desired behaviour in the respective class without having to touch the implementation of the other classes or the inheritance structure)

If this is impossible I would also appreciate an explanation to that effect.

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")

EDIT:

As mentioned above I am aware that I can achieve the desired behaviour in different ways such as changing the class structure (as below). However the question is really more about wether python allows doing it with changes only to the Child class. If this is actually impossible (not merely undesirable) in python, an explanation why would do more to answer my question than providing alternative implementations that change anything beyond the implementation of the Child class.

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
Chris
  • 445
  • 6
  • 11
  • While there's probably a solution if you're willing to bend over backwards enough, this sounds like something that will just introduce needless confusion and surprising behaviour. What behaviour exactly do you need to change, perhaps there's a different way of achieving that. – deceze Oct 28 '20 at 09:35
  • 1
    It seems like this Grandchild class should be a direct descendent from Parent class. Is there some real necessity to inherit something from Child? This is not avoiding to provide an answer, is just how the OOP should be and sounds like you trying to do something OOP is not suitable for. Also, maybe you are trying to do a new class that should be "brother" to Child. – madtyn Oct 28 '20 at 09:43
  • So basically I have an existing Inheritance structure where one class (aka `Child`) provides common functionality to all its children but also can be instanciated itself. However it needs only a subset of the required constructor arguments of its children (`GrandChild`). Currently all classes uses the constructor from the top level class (`Parent`) which leads to having to pass unnecessary arguments when instanciating the class in the middle (`Child`). I want to change the constructor of this class while minimising the changes that I need to do to the existing classes / inheritances. – Chris Oct 28 '20 at 09:45
  • @madtyn : The actual use case / inheritance tree is significantly more complex with several classes on each level providing common functionality for all of its subclasses. As mentioned I am trying to do it in the above described way (which is probably not the cleanest if feasabile at all) to minimise the changes I have to do to the existing class structure. – Chris Oct 28 '20 at 09:48
  • 2
    Hi Chris, I understand that you are looking for something that "just works", but normally you want something that not only works, but also makes sense and is robust for future modifications. It makes no sense to create a subclass that has only a portion of the attributes of the super class, because there is an is-a relation between the two. A subclass should add attributes and/or modify behaviour of the super class. – Marko Oct 28 '20 at 09:56
  • @Marko: The above code is not the actual use case that motivated me to write this question but a highly reduced example highlighting the main purpose of the question. In the actual use case all classes add significant functionality that is shared by each of these subclasses (of which there are multiple on each level). Just removing one level of inhertiance is therefore not an option unfortunately. – Chris Oct 28 '20 at 10:00
  • Well, without being able to look at the code, I'm just guessing, but... Could all these attributes currently in the Child init method but not being used further down be used to make another class, probably out of the inheritance line, so this new class can do delegated tasks related to that data by composition? Maybe you can separate and abstract some of the data in another class and call it and you are making classes bigger with more responsibility than they should receive. – madtyn Oct 28 '20 at 10:16
  • @madtyn In fact I have already found a solution to the original code, changing the inheritance structure. However I felt that there must be a way to somehow overwrite a method only on one inheritance level while leaving the subclasses untouched. By now this question is mainly about feeding that curiosity and less about a solution for the original problem. – Chris Oct 28 '20 at 10:22
  • Yes, I understand. But reading you here I would say that this is a design problem causing you the necessity of doing something taht would be a little dirty in OOP (because I think this is a OOP problem more than a language problem). Now for your curiosity, you can call directly on Parent through super(Parent, self) line in your Grandchild init method. – madtyn Oct 28 '20 at 11:31

3 Answers3

3

I have found a way using _init_subclass to make sure that all subclasses of Child use the constructor of Parent instead of the one defined in Child inspired by this post:

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

Even though this is a bit hacky it provides the desired functionality of actually bypassing Childs init method

Chris
  • 445
  • 6
  • 11
  • Interresting ! But are you sure that `super().__init_subclass__(**kwargs)` is really necessary ? When commented out, it works as well. – dspr Oct 28 '20 at 11:25
  • Not sure if it is actually necessary. Kind of copied that from the linked post, will check if it works without as well. – Chris Oct 28 '20 at 11:48
  • Nice. I read that this ```__init_subclass__``` method is actually meant for metaclasses (classes of classes, like type()), but I see that using this construction you can bypass the init of Child. – Marko Oct 28 '20 at 12:27
  • In fact the `super.__init_subclass` was not necessary and I removed it accordingly. – Chris Oct 28 '20 at 12:27
0

You could do :

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")

EDIT : you could also replace the optional parameter by a mandatory one in GrandChild :

class GrandChild(Child):
    def __init__(self, a, b):
        super().__init__(a, b)
dspr
  • 2,383
  • 2
  • 15
  • 19
  • While this answer would result in the code running without error it does change the implementation of the `GrandChild` class in such a way that the second parameter is not mandatory anymore. I would like to avoid that. – Chris Oct 28 '20 at 09:59
  • Well in that case you could also remove the optional parameter and pass `Child("c", None")`to build a Child instance ? I'm afraid there is not a perfect solution here. In some other languages you can call a specific level of the hierarchy directly (e.g. super super) but I don't think it's possible in Python. – dspr Oct 28 '20 at 10:03
  • Manually passing `None` for the superfluous parameters in the `Child` constructor is the current behaviour which I want to change since I find it confusing. In another language like Java I would probably use method overloading for this problem but unfortunately this does not seem to work in python. There is an `overload` decorator but it does not allow signatures with different number of parameters unfortunately. – Chris Oct 28 '20 at 10:07
  • I edited my post to propose a solution for the issue you pointed in you previous comment – dspr Oct 28 '20 at 10:09
  • @dspr although your code does not give an error, the b attribute of GrandChild is now None instead of "e". – Marko Oct 28 '20 at 10:09
  • No because you pass "d" to the constructor, so the optional is overridden – dspr Oct 28 '20 at 10:11
  • @dspr: Thanks for the proposed solution. As mentioned in my question however I was aware of this option and I am explicitly looking for a way to achieve the behaviour without changing the GrandChild class (of which there are many different ones in the actual code / inheritance structure) – Chris Oct 28 '20 at 10:16
  • @dspr Have you tried it? I've added print(grandchild.b) to your code, and it prints None, as it should, because it uses the init of Child. – Marko Oct 28 '20 at 10:37
  • You 're right sorry : it's because I passed `None` to super in the Child class instead of passing `b`. I fixed that and it works as I said now. – dspr Oct 28 '20 at 10:46
  • The point is that I would really like the constructor of `Child` to have a different signature from the other classes. Also I don't want to allow passing the additional argument to the constructor of `Child` as this makes no sense in the original UseCase – Chris Oct 28 '20 at 10:55
  • Sorry this answer was for @Marko, I forgot to mention it – dspr Oct 28 '20 at 10:59
0

This code might do the trick, adding a few lines to the suggestion of @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
Marko
  • 372
  • 2
  • 7
  • This seems to be the best solution that I have seen so far as it achieves the desired behaviour within the given constraint of only changing the `Child` class. However this obviously still causes the `GrandChild` class to use the \_\_init\_\_ method of `Child` instead of `Parent`. I am still curious if there is any way of avoiding that (e.g. some decorator or metaclass) or if this is actually impossible, so I will hold back a bit with accepting your answer. – Chris Oct 28 '20 at 10:48
  • Also Ideally I would like the init method of the `Child` class to not accept the second parameter at all. – Chris Oct 28 '20 at 10:51
  • Hi @Chris, I don't think it is possible to have the init of the GrandChild class skip the one of the Child class. What you could do in the Child class, to prohibit passing an argument to b, is to add a few lines to the if block. I will add them to my code. – Marko Oct 28 '20 at 10:59
  • I actually found a way to bypass it I think (see my own answer to this question) – Chris Oct 28 '20 at 11:14