Get up to 80 % extra points for free! More info:

Lesson 7 - Inheritance and polymorphism in Python

In the previous lesson, Arena with warriors in Python, we made an arena that simulates two warriors in battle. In today's Python tutorial, we're going to expand our knowledge of object-oriented programming. In the introduction of this course, we mentioned that the OOP is based on three fundamental concepts: encapsulation, inheritance and polymorphism. We're already familiar with the encapsulation and learned to use underscores to declare private class members. Today, we're going to be taking a look at the two remaining concepts.

Inheritance

Inheritance is one of the basic features of OOP and is used to create new data structures based on old ones. Let's look at a simple example:

Imagine that we were hired to create a database that will keep track of animals at a zoo :) There will be two types of users in our system: users and administrators. Users will be common zookeepers who will be able to edit information about the animals, e.g. their weight or wingspan. The administrator will be able to modify animal data as well and add or delete animals from the database. Administrators will have an extra attribute, a phone number so they can be contacted in case of system failure. It'd be pointless and confusing to redeclare the new class completely since they have many attributes and methods in common. Both the user and the administrator will have names, ages and be able to log in and log out. Today will mostly be about going over the concepts, we'll program a fully functioning example in the next lesson (the following code is just a draft):

class User:

    def __init__(self, name, password, age):
        self.__name = name
        self.__password = password
        self.__age = age

    def log_in(self, password):
    ...

    def log_out(self):
    ...

    def set_weight(self, animal):
    ...

    ...

We've just made a general outline of the class, but I'm sure you get the gist of it. Without knowing what inheritance is, we would define our Admin class like this:

class Admin:

    def __init__(self, name, password, age, phone_number):
        self.__name = name
        self.__password = password
        self.__age = age
        self.__phone_number = phone_number

    def log_in(self, password):
    ...

    def log_out(self):
    ...

    def set_weight(self, animal):
    ...

    def add_animal(self, animal):
    ...

    def remove_animal(self, animal):
    ...

    ...

We can see that our code is very redundant in this class. All further changes would have to take a place in both classes, it'd make the code very hard to maintain. Now, we'll declare the Admin class using inheritance. We'll let the Admin class to be inherited from the User class. We don't have to declare attributes and methods again, Python will add them automatically:

class Admin(User):

    def __init__(self, name, password, age, phone_number):
    super().__init__(name, password, age)
    self.__phone_number = phone_number

    def add_animal(self, animal):
    ...

    def remove_animal(self, animal):
    ...

    ...

We see that we've used parentheses () for inheritance. We write there the name of the class we're inheriting from.

Don't mind the "odd" super() method for now, we'll explain it later as it's necessary if we want to call the parent's method. But back to the example.

In the example above, the private attributes won't be accessible to the descendant. Only the attributes and methods will. Private attributes and methods are understood as a special logic that belongs to a certain class. Meaning that, they are hidden from descendants - even a descendant is actually using this logic, it's unable to modify it. To achieve the desired result, we'll use a new access modifier - an underscore _, which works just like two underscores __ but allows the inheritance of these attributes. For programmers, it means. "You can see this, but don't use it!". We call such attributes protected.

The beginning of the User class would look like the following:

class User:

    def __init__(self, name, password, age):
        self._name = name
        self._password = password
        self._again = again

Now, if we created user and administrator instances, they'd both have name attributes and log_in() methods. Python will inherit the User class and redeclare all its attributes automatically.

The advantages of using inheritance are clear. We don't have to copy the same attributes to two classes. All we have to do is declare new ones. The rest will be inherited. The benefits are tremendous, we can extend existing components of new methods and reuse them. We don't have to write lots of redundant code, and most importantly - when we change a single attribute in the parent class, this change is then inherited everywhere automatically. We don't need to change something manually in 20 classes, which could potentially cause errors. We're humans and we'll always make mistakes, we have to choose the right programming techniques to make as less mistakes as possible.

The parent class is sometimes called the ancestor, our User class here, and the class that inherits from it, a descendant, our Admin class. Descendants may add new methods or override the existing methods of the parent class (see example below). Sometimes, you may encounter the terms superclass and subclass which essentially mean the same thing.

Another way to design the object model would be to create a User parent class whose sole purpose would be inheritance. The User would be then inherited by Attendant and Admin classes. This technique would be useful if we had lots of user types. In that case, we're talking about a class hierarchy, which we'll get into further along in this online course. Our example was simple so two classes were enough. There are also design patterns that are established and approved object models used for common situations. All of that stuff is advanced and interesting, and I promise we'll get to them later as well. In object modeling, we draw inheritance as an empty arrow pointing to the parent class. In our case, the graphical notation would look like this:

Object inheritance – graphical notation - Object-Oriented Programming in Python

Class type check

In Python, we can ask whether an object is an instance of a particular class.

1. We can use the build-in type() class for it:

>>> a = 1
>>> type(a) == type(1)
True
>>> type(a) == type("Python!")
False
>>> type(a) == int
True
>>> type(a) == str
False

2. But it's better to use the build-in isinstance() function:

>>> a = 1
>>> isinstance(a, 1)
True
>>> isinstance(a, "Python")
False
>>> isinstance(a, int)
True
>>> isinstance(a, str)
False

All objects in Python inherit from the object class. In Python 2.X, it was necessary to inherit our classes from this one. Now, Python will inherit it automatically.

Languages can support either single or "multiple inheritance". With the single inheritance, a class can inherit only from one other class. With the "multiple inheritance", a class can inherit from several classes at the same time. Python supports multiple inheritance, similarly as the C++ language does.

Polymorphism

Don't be scared of the obscure name of this technique, it's actually very simple. Polymorphism allows us to use a unified interface to work with objects of different types. Imagine that we have, for example, many objects representing geometric shapes (circles, squares, triangles, and so on). It'd certainly be helpful if we could communicate with them in some unified way even though they're different. We can create a GeometricShape class containing a color attribute and a render() method. All the geometric shapes would then inherit the interface from this class. Now you may be thinking, "But the circle and square object would render differently!." Well, polymorphism allows us to override the render() method in every subclass so it will do what we want. The interface will be unified and we won't have to think about which method to call to render different objects.

Polymorphism is often explained using animals. All having a speak() method in their interface, but each animal performs it differently.

Polymorphism - Object-Oriented Programming in Python

The essence of polymorphism is a method or methods, that all the descendants have defined with the same heads, but with different method bodies. We'll use polymorphism along with inheritance in the next lesson, Arena with a mage in Python (inheritance and polymorphism), on our warriors in the arena. We'll add a mage who will inherit warrior's attributes and behavior and override the warrior's attack() method to use mana. We won't be able to tell that he's not a pure warrior from the outside since he will have the same interface. It'll be fun :)


 

Previous article
Arena with warriors in Python
All articles in this section
Object-Oriented Programming in Python
Skip article
(not recommended)
Arena with a mage in Python (inheritance and polymorphism)
Article has been written for you by David Capka Hartinger
Avatar
User rating:
1 votes
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities