Learning Management Platform for Written Tutorial Series.

Python 3 Inheritance - With complete working source code

Python 3 Inheritance - With complete working source code

Python Inheritance

In this lesson, we will look at the principle of inheritance in python. Inheritance is one of the fundamental principles of objected oriented programming. It is a feature that allows a child class to inherit variables and methods that have been defined in a parent class. This allows us to define something only once and use it multiple times in objected that are related to the parent.

Topics to be covered

We will cover the following topics in this lesson

  • Python Inheritance syntax and super
  • Method Overriding
  • Class variables and methods accessibility
  • Multiple inheritance

Python Inheritance syntax

In this section, we will look at how to create a parent class that child classes can inherit from.

Let's start with the parent class

class Person:
    _name = ''
    _gender = ''

    def __init__(self,name,gender):
        self._name = name
        self._gender = gender


    @property
    def name(self):
        return self._name


    @property
    def gender(self):
        return self._gender

HERE,

  • class Person(): defines a class called Person
  • _name = ''; _gender = '' defines two weakly private variables _name and _gender.
  • def __init__(self,name,gender): defines the class constructor that accepts the name and gender parameters. self._name = name sets the value of name variable to self._name variable. self._gender = gender assigns the gender property.
  • @property; def name(self): defines a name property that returns the value of the variable _name.
  • @property; def gender(self): defines a gender property that returns the value of the variable _gender.

Let's now create two child classes Customer and Employee that will inherit from the Person class.

class Customer(Person):
    _balance = 0

    def __init__(self, name, gender,balance):
        super().__init__(name, gender)
        self._balance = balance

    @property
    def balance(self):
        return self._balance


class Employee(Person):
    _department = ''

    def __init__(self, name, gender,department):
        super().__init__(name, gender)
        self._department = department


    @property
    def department(self):
        return self._department

HERE,

  • class Customer(Person): defines a class Employee that inherits from Person. We do this about passing in the parent class Person in the parenthesis Customer(Person).
  • _balance = 0 defines a weakly private variable that is unique to the Customer class.
  • def __init__(self, name, gender,balance): defines the class constructor that accepts the name, gender, and balance parameters.
  • super is a special method that we use to access the parent class from which the child inherits from. In our example, Person is the parent class which can access from our child class using super
  • @property; def balance(self): defines a property balance that returns the value of the _balance variable.
  • class Employee(Person): defines a class Employee that inherits from Person. We do this about passing in the parent class Person in the parenthesis Employee(Person). _department = '' defines a weakly private variable that is unique to the Employee class.def __init__(self, name, gender,department): defines the class constructor that accepts the name, gender, and department parameters. @property; def department(self): defines a property department that returns the value of the _department variable.

Let's now create instances of our two child classes

customer = Customer('John Smith','Male',513.6)
employee = Employee('Amanda Kazembe','Female','Finance')

print(f'The customer is {customer.gender}, and the name is {customer.name}. The balance is {customer.balance}')
print(f'The employee is {employee.gender}, and the name is {employee.name}. The department is {employee.department}')

The complete source code for our application is as follows

class Person:
    _name = ''
    _gender = ''

    def __init__(self,name,gender):
        self._name = name
        self._gender = gender


    @property
    def name(self):
        return self._name


    @property
    def gender(self):
        return self._gender


class Customer(Person):
    _balance = 0

    def __init__(self, name, gender,balance):
        super().__init__(name, gender)
        self._balance = balance

    @property
    def balance(self):
        return self._balance


class Employee(Person):
    _department = ''

    def __init__(self, name, gender,department):
        super().__init__(name, gender)
        self._department = department


    @property
    def department(self):
        return self._department


customer = Customer('John Smith','Male',513.6)
employee = Employee('Amanda Kazembe','Female','Finance')

print(f'The customer is {customer.gender}, and the name is {customer.name}. The balance is {customer.balance}')
print(f'The employee is {employee.gender}, and the name is {employee.name}. The department is {employee.department}')

Executing the above code produces the following results

The customer is Male, and the name is John Smith. The balance is 513.6
The employee is Female, and the name is Amanda Kazembe. The department is Finance

Method Overriding

Method overriding allows us to change a method definition in the child class that was already defined in the parent. Let's add a method to our Person class called generation.

def generation(self):
	return 'first generation'

We can now call our generation method defined in the parent like so

print(f'i am a direct descend of the {customer.generation()}')

Executing the above code produces the following results

i am a direct descend of the first generation

Let's now override the generation method by implementing it in the Customer class.

Add the following method to the Customer class.

def generation(self):
	return 'second generation'

We can now call the generation methods on both classes like so

print(f'i am a direct descend of the {customer.generation()}')
print(f'i am a direct descend of the {employee.generation()}')

Executing the above code produces the following results

i am a direct descend of the second generation
i am a direct descend of the first generation

Note: The customer class says second generation while the employee class says first generation. This is because we overrode the generation class in Customer while in Employee, we didn't do that.

Class variables and methods accessibility

Access modifier limit access to class resources like variables and methods. Unlike other languages like C# or Java, Python does not strictly enforce access modifiers. We instead use a conversion to let other programmers know that this is protected or weakly private by using an under like so _name. If we want to go full hardcore then we do it like so __name. We covered access modifiers in the previous lesson on classes so we will just covere the theory in this section.

Variables and methods that start with a single underscore like _balance are safe to use in child classes. They are protected to borrow a term from other languages like C#.

Variables and methods that start with double underscores like __religion are to be avoided at all costs. They are strongly private to the parent class. This means the parent class can drop them without caring about the child classes. Instead you should access should strongly private entities using public methods and properties if the parent class allows you to do so.

Multiple inheritance

I have programmed in a number of languages like PHP, Java, C#, and many others but I am really into python. It's like the black sheep of programming languages. Multiple Inheritance is a taboo in other languages but not with python. We are allowed to do it and so we shall do exactly that in this lesson.

We created a parent class Person then we derived two child classes Customer and Employee. Multiple inheritance allows us to create a class that inherits from more than one parent. For this little practical example, we will derive a third class InternalCustomer. An internal customer is both an employee and a customer. So we will basically just create a class that inherits both from Customer and Employee.

Before we do that, let's look at some of the challenges of multiple inheritance. Customer and Employee both inherit from Person and Customer even overrides the generation method. If InternalCustomer inherits both from Customer and Employee which parent methods exactly will be executed since both have similar methods?

Method Resolution Order (MRO) is a built-in mechanism that python uses to determine which method will be implemented in the child class. Since this is an introductory lesson, we will not cover MRO in much detail. We will do that in a separate lesson dedicated to MRO.

One last thing that we need to take care of when dealing with multiple inheritance is passing in parameters. All you need to know is that we pass in unlimited number of positional and keyword arguments in constructors and methods. Again this is slightly advanced and much details will be revealed in the follow up lessons .

Our complete multiple inheritance source code is as follows

class Person:
    _name = ''
    _gender = ''

    def __init__(self,*args,**kwargs):
        self._name = kwargs['name']
        self._gender = kwargs['gender']


    @property
    def name(self):
        return self._name


    @property
    def gender(self):
        return self._gender


    def generation(self):
        return 'first generation'


class Customer(Person):
    _balance = 0

    def __init__(self, *args,**kwargs):
        super(Customer,self).__init__(*args,**kwargs)
        self._balance = kwargs['balance']

    @property
    def balance(self):
        return self._balance


    def generation(self):
        return 'second generation'


class Employee(Person):
    _department = ''

    def __init__(self, *args,**kwargs):
        super(Employee,self).__init__(*args,**kwargs)
        self._department = kwargs['department']


    @property
    def department(self):
        return self._department


class InternalCustomer(Employee,Customer):
    def __init__(self,*args,**kwargs):
        super(InternalCustomer,self).__init__(*args,**kwargs)

print(InternalCustomer.mro())

ic = InternalCustomer(name='Rodrick',gender='Male',balance=513.6,department='Engineering')

print(f'The internal customer is {ic.gender}, and the name is {ic.name}. The balance is {ic.balance}')

HERE,

  • class InternalCustomer(Employee,Customer): defines a class that inherits from both Employee and Customer
  • def __init__(self,*args,**kwargs): defines the class constructor methods for all of our classes. Note: how we are using *args for unlimited positional parameters and **kwargs for unlimited keyword parameters.
  • super(InternalCustomer,self).__init__(*args,**kwargs) calls the parent class constructor using the super method. Notice how we are passing in the Classname and self parameter to the super method. We then call the __init__ function and pass in the arguments. This logic applies to Customer and Employee classes.
  • print(InternalCustomer.mro()) calls the mro on the class InternalCustomer directly and not its instance. This method shows us how python will resolve methods that are called in the parent classes.
  • ic = InternalCustomer(name='Rodrick',gender='Male',balance=513.6,department='Engineering') creates an instance of our class InternalCustomer and uses keyword parameters to pass in values.

Executing the above code produces the following results.

[<class '__main__.InternalCustomer'>, <class '__main__.Employee'>, <class '__main__.Customer'>, <class '__main__.Person'>, <class 'object'>]
The internal customer is Male, and the name is Rodrick. The balance is 513.6

HERE,

  • [<class '__main__.InternalCustomer'>, <class '__main__.Employee'>, <class '__main__.Customer'>, <class '__main__.Person'>, <class 'object'>] shows us the Method Resolution Order hierarchy. First python checks for the method in InternalCustomer followed by Employee, then Customer then Person then object as the last class. object is the parent of all classes in python that is why is is included in MRO resolution.

Summary

Inheritance is a powerful feature that allows us to define common functionality in a parent class and derive child classes from it that automatically have the functionality that has been defined in the parent class. Overriding allows us to change the implementation that has already been defined in the parent class.

Multiple inheritance is a feature that allows a child class to inherit from more than one parent. Python uses Method Order Resolution (MRO) to determine which method should be called from the parent class if more than one parent class exists.

What next?

If you enjoyed this lesson then show us your appreciation by creating a free accounts on our site. As always we appreciate your comments down below.


...