# About



Inheritance is one of the ['four pillars'][four-pillars] of Object Oriented Programming (`OOP`).

In situations where only a small amount of functionality needs to be customized for a new class, `inheritance` allows code re-use from one or more parent classes, and can help make programs cleaner and more maintainable.



## Inheritance



`Inheritance` describes `is a kind of` relationship between two or more classes, abstracting common details into super (_base_ or _parent_) class and storing specific ones in the subclass (_derived class_ or _child class_).



To create a child class, specify the parent class name inside the pair of parenthesis, followed by it's name.

Example

```python

class Child(Parent):  

   pass

```

Every child class inherits all the behaviors (_attributes, constructors, methods_) exhibited by their parent class.

 



## Single Inheritance



When a derived (or child) class inherits only from one base (or parent) class, it is known as _single inheritance_.





```python

# The parent or base class.

class Person:



    def __init__(self, fname, lname):

        self.fname = fname

        self.lname = lname



# The child or derived class, inheriting from Person.                

class Employee(Person):



    all_employees = []

    def __init__(self, fname, lname, empid):

        # Using the Parent constructor to create the base object.

        Person.__init__(self, fname, lname)



        # Adding an attribute specific to the Child class.

        self.empid = empid



        Employee.all_employees.append(self)

```

`Employee` class is derived from `Person`.

Now, we can create an `Employee` object.





```python

...

p1 = Person('George', 'smith')

print(p1.fname, '-', p1.lname)

e1 = Employee('Jack', 'simmons', 456342)

e2 = Employee('John', 'williams', 123656)

print(e1.fname, '-', e1.empid)

print(e2.fname, '-', e2.empid)

```

After running the program we will get the following output

```bash



George - smith

Jack - 456342

John - 123656

```

## Multiple Inheritance

As we've seen, `single inheritance` is where a class inherits directly from another class. 

On the other side, `multiple inheritance` is a Python feature that allows a child class to inherit characteristics and methods from more than one parent class.



```python

class SubclassName(BaseClass1, BaseClass2, ...):

    pass

```

### Multiple Inheritance and the Diamond Problem



The "diamond problem" (also known as the "deadly diamond of death") refers to an ambiguity that occurs when two classes B and C inherit from a superclass A, while another class D inherits from both B and C. If A has a method "m" that B or C (or even both of them) has overridden, and if it does not override this method, the question becomes which version of the method D inherits. It's possible that it's from A, B, or C.

Let's have a look at the problem using an example:



```python

class A:

    def m(self):

        print("m of A called")

class B(A):

    def m(self):

        print("m of B called")

class C(A):

    def m(self):

        print("m of C called")

class D(B,C):

    pass

```

If we call an instance x of class D, we will get the output as `m of B called`. But if we interchange the order of inheritance in class D i.e. `Class D(C, D)`. We will get the output as `m of C called`. 

To solve the diamond problem in python, we will look into a new method `mro()`.

### Method resolution order(MRO)



To get the method resolution order of a class we can use either `__mro__` attribute or `mro()` method. By using these methods we can display the order in which methods are resolved. For Example



```python

class A:

    def m(self):

        print(" m of A called")

class B:

    def m(self):

        print(" m of B called")

  

# classes ordering

class C(A, B):

    def __init__(self):

        print("Constructor C")

  

r = C()

  

# it prints the lookup order 

print(C.__mro__)

print(C.mro())

```

The output 

```cmd

Constructor C

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

```

### Mixins

A mixin is a type of multiple inheritance that is unique. Mixins are typically employed in one of two scenarios:



1. We wish to give a class a number of optional features.

1. We want to use a specific feature in a variety of classes.



For example 

```python

class A1(object):

    def method(self):

        return 1



class A2(object):

    def method(self):

        return 2



class B1(object):

    def usesMethod(self):

        return self.method() + 10



class B2(object):

    def usesMethod(self):

        return self.method() + 20



class C1_10(A1, B1): pass

class C1_20(A1, B2): pass

class C2_10(A2, B1): pass

class C2_20(A2, B2): pass

```

Mixins helps us to recombine functionalities with different choices of base classes.

#### Pros and Cons of Mixins

| Advantages | Disadvantages |

|:-- | :-- |

|Mixin classes tend to be simple because they represent simple orthogonal concepts. | Execution of statements at run time tends to jump around in different mixins, making it hard to follow and debug|

|Helps us to recombine functionalities with different choices | Potential for long compile times|

## __super()__

In a nutshell, `super()` gives us access to methods in a superclass from the subclass that inherits from it.

`super()` by itself returns a temporary object of the superclass, which may subsequently be used to call the methods of that superclass.



But why we want to use `super()`?



Using `super()` to call already created methods avoids having to rebuild those methods in our subclass and allows us to swap out superclasses with little code modifications.



[four-pillars]: https://www.educative.io/edpresso/what-are-the-four-pillars-of-oops-in-python



[four-pillars]: https://www.educative.io/edpresso/what-are-the-four-pillars-of-oops-in-python



