pythonoopinheritance

Conditional Inheritance based on arguments in Python


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!


Solution

  • 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'