5

Being new to OOP, I wanted to know if there is any way of inheriting one of multiple classes based on how the child class is called in Python. The reason I am trying to do this is because I have multiple methods with the same name but in three parent classes which have different functionality. The corresponding class will have to be inherited based on certain conditions at the time of object creation.

For example, I tried to make Class C inherit A or B based on whether any arguments were passed at the time of instantiating, but in vain. Can anyone suggest a better way to do this?

class A:
    def __init__(self,a):
        self.num = a

    def print_output(self):
        print('Class A is the parent class, the number is 7',self.num)

class B:
    def __init__(self):
        self.digits=[]

    def print_output(self):
        print('Class B is the parent class, no number given')

class C(A if kwargs else B):
    def __init__(self,**kwargs):
        if kwargs:
            super().__init__(kwargs['a'])
        else:
            super().__init__()

temp1 = C(a=7)
temp2 = C()

temp1.print_output()
temp2.print_output()

The required output would be 'Class A is the parent class, the number is 7' followed by 'Class B is the parent class, no number given'.

Thanks!

Adithya Shiva
  • 53
  • 1
  • 3
  • 1
    You don't want a class `C` at all; you just want a function that returns either an instance of `A` or an instance of `B`, depending on the argument's passed. – chepner Jan 16 '21 at 20:52
  • 3
    You should search for more about ‘factory’ methods/functions - this is jargon/terminology for a function which is called with some data that helps it decide which type of instance to return to the caller. IMO a factory implementation isn’t in itself very elegant (usually if-else, or perhaps dictionary-based for more complex choices) but has the advantage of of centralising in one place the decision-making about what class to create an instance of - in your case choosing between A or B. @chepner didn’t mention factory, but that’s what they are referring to. – DisappointedByUnaccountableMod Jan 16 '21 at 20:53
  • In the actual code, class C is required because it processes data obtained from the parent classes. It needs to inherit methods either from Class A or Class B based on some initial conditions, do a bunch of stuff and then return some data based on the same. I can certainly do it by just copying all the methods in C to A and B and call the parent classes directly, just wanted to know if it is possible any other way. Thanks for your help! – Adithya Shiva Jan 16 '21 at 21:00
  • But don’t do what the answer from @SamiTahri suggests and dynamically create a class. That approach is a debugging and maintenance nightmare. Use statically declared classes. Yes it’s possible in Python, but that doesn’t make it advisable/sensible. – DisappointedByUnaccountableMod Jan 16 '21 at 21:00

2 Answers2

6

Whether you're just starting out with OOP or have been doing it for a while, I would suggest you get a good book on design patterns. A classic is Design Patterns by Gamma. Helm. Johnson and Vlissides.

Instead of using inheritance, you can use composition with delegation. For example:

class A:
    def do_something(self):
        # some implementation

class B:
    def do_something(self):
        # some implementation

class C:
    def __init__(self, use_A):
        # assign an instance of A or B depending on whether argument use_A is True
        self.instance = A() if use_A else B()

    def do_something(self):
        # delegate to A or B instance:
        self.instance.do_something()

Update

In response to a comment made by Lev Barenboim, the following demonstrates how you can make composition with delegation appear to be more like regular inheritance so that if class C has has assigned an instance of class A, for example, to self.instance, then attributes of A such as x can be accessed internally as self.x as well as self.instance.x (assuming class C does not define attribute x itself) and likewise if you create an instance of C named c, you can refer to that attribute as c.x as if class C had inherited from class A.

The basis for doing this lies with builtin methods __getattr__ and __getattribute__. __getattr__ can be defined on a class and will be called whenever an attribute is referenced but not defined. __getattribute__ can be called on an object to retrieve an attribute by name.

Note that in the following example, class C no longer even has to define method do_something if all it does is delegate to self.instance:

class A:
    def __init__(self, x):
        self.x = x

    def do_something(self):
        print('I am A')

class B:
    def __init__(self, x):
        self.x = x

    def do_something(self):
        print('I am B')

class C:
    def __init__(self, use_A, x):
        # assign an instance of A or B depending on whether argument use_A is True
        self.instance = A(x) if use_A else B(x)

    # called when an attribute is not found:
    def __getattr__(self, name):
        # assume it is implemented by self.instance
        return self.instance.__getattribute__(name)

    # something unique to class C:
    def foo(self):
        print ('foo called: x =', self.x)

c = C(True, 7)
print(c.x)
c.foo()
c.do_something()
# This will throw an Exception:
print(c.y)

Prints:

7
foo called: x = 7
I am A
Traceback (most recent call last):
  File "C:\Ron\test\test.py", line 34, in <module>
    print(c.y)
  File "C:\Ron\test\test.py", line 23, in __getattr__
    return self.instance.__getattribute__(name)
AttributeError: 'A' object has no attribute 'y'
Booboo
  • 38,656
  • 3
  • 37
  • 60
  • This is really useful for the project I'm working on right now as there is a situation where parent class of one of my class depends on user's choice. I'd like to ask if there is way to refer to chosen parent class' attributes from self instead of self.instance as not to rewrite a lot of code (i.e., provide fake inheritance) ? – Lev Barenboim Aug 10 '21 at 21:53
  • @LevBarenboim I just want to make sure I understand your question. We are talking about my *composition* implementation where class `C` delegates to class `A` for example and you want access an attribute of `A`, for example `x` without having to say `self.instance.x` but rather `self.x` just as if `A` inherited from `C`. Right? – Booboo Aug 10 '21 at 22:20
  • Yes, indeed. Sorry if I didn't make it clear from my original comment. – Lev Barenboim Aug 10 '21 at 22:24
  • 1
    @LevBarenboim You were clear -- I just wanted to make sure. See my update to the question. – Booboo Aug 10 '21 at 22:55
  • This won't work off-the-shelf for things like len() and index lookup. You'll have to add wrappers for .__len__ and .__getitem__ returning self.instance.__len__ and self.instance.__getitem__. – Yash Jakhotiya Jul 27 '22 at 07:31
0

I don't think you can pass values to the condition of the class from inside itself.

Rather, you can define a factory method like this :

class A:
    def sayClass(self):
        print("Class A")
class B:
    def sayClass(self):
        print("Class B")


def make_C_from_A_or_B(make_A):
    class C(A if make_A else B):
        def sayClass(self):
            super().sayClass()
            print("Class C")
    return C()

make_C_from_A_or_B(True).sayClass()

which output :

Class A
Class C

Note: You can find information about the factory pattern with an example I found good enough on this article (about a parser factory)

Sami Tahri
  • 1,077
  • 9
  • 19
  • 1
    Personally I would never use this approach of dynamically creating a new class C - if as is possible multiple different C are created it would be a nightmare to debug and to maintain. Instead statically declare and when creating an instance of C provide the data needed. – DisappointedByUnaccountableMod Jan 16 '21 at 20:59
  • I do agree, but answering the asked question. The factory pattern is described on the link, and usually make multiple objet inherit from one base object where each implementation differ (like parser/writer). – Sami Tahri Jan 16 '21 at 21:06
  • Oh and @barny, I didn't even knew the Class X(X1 if cond else X2) was even a thing as it seems delirious to me :D – Sami Tahri Jan 16 '21 at 21:09
  • Delirious seems a very appropriate adjective :-) – DisappointedByUnaccountableMod Jan 16 '21 at 21:11
  • This is a "feature" of python because it is dynamic. I guess something like this in Java would not even be a thing. – Sami Tahri Jan 16 '21 at 21:13
  • 1
    While this syntax may be possible in Python, that doesn’t make it advisable/sensible unless adopted in full awareness of the long-term impact it might have for code that has to be debugged and maintained - seems very dangerous to me. – DisappointedByUnaccountableMod Jan 16 '21 at 21:17