Inheritance is the capability of one class (child/derived/sub class) to derive or inherit the properties or attributes from some another class (parent/base class) . Inheritance increases reusability of a code. We don’t need to write the same code again and again. Inheritance it allows programmers to add more features to a class or modify the existing behavior.

Simple Inheritance

Have a look at this simple inheritance procedure :

get_name method is defined in Person class not in Employee class. When we inherit from base class (at line 9) we get its properties and attributes to the child class. That’s why we are getting access to get_name method at child class and its objects (at line 18 and 19) .

Attributes, properties or methods of base class will be available to child class but not the vice versa. It we write person.is_employee() then we will face an error like : AttributeError: ‘Person’ object has no attribute ‘employee’ because, is_employee() method defined in child class, not in the parent class.

Multilevel inheritance

Python support multilevel inheritance. When a class is derived from a class which is also derived from another class (a class having more than one parent classes) such inheritance is called Multilevel Inheritance. The level of inheritance can be extended to any number of level depending upon the relation.

Here, class A has only method_from_class_a method, class B has method_from_class_a and method_from_class_b methods (because, class B extends class A) and class C has method_from_class_a , method_from_class_b and method_from_class_c methods (because, class C extends B and class B extends c).

Inheritance is transitive in nature (which means that if class B inherits from another class A, then all the subclasses of B would automatically inherit from class A).

Multiple inheritance

Python supports multiple inheritance, where a class can have multiple parent classes.

Here, class C extends both class A and class B , that’s why methods of class A and class B are available to class C.

Method Resolution Order (MRO)

Let’s start enjoying the game of this article!

Here, we have two speak methods in class A and class B . class C extends both class A and class B . So, class C gets both speak methods. Then what will be output at line 16? Which speak method will be called? Confusing, NO?

For a class hierarchy, Python needs to determine which class to use when attempting to access an attribute by name. To do this, Python considers the ordering of base classes.

For the previous code, speak method of class A will be executed and we will get “class A speaking” output. It we change class C(A, B) (at line, 11) to class (B, A) then speak method of class B will be executed and we will get “class B speaking” output.

Let’s change code of class C like :

class C(A, B):

def speak(self):

print("class C speaking")

Here, speak method of class C will override previous speak method. And c.speak() will print “class C speaking”.

Now, according to our discussion on MRO guess what will be output of this code :

Congratulation! you’re correct! Output will be “A”.

Little philosophy about Inheritance

Consider a relationship scenario like:

class Contact:

name = TextField()

email = EmailAddressField()





class Person(Contact):

first_name = TextField()

last_name = TextField()





class Friend(Person):

relationship_details = TextField()





class FamilyMember(Person):

relationship_details = TextField()

family_members = IntegetField()

Notice that, both Friend and FamilyMember have relationship_details attribute and both of them are identical. If FamilyMember would extend Friend class then we only needed to write family_members attribute in FamilyMember class. But we didn’t do that. Because, it’s not necessarily true that a family member will also be a friend but a family member necessarily will be a person. So, the philosophy is, for inheritance, class structure should reflects the correct relationship among classes.

Some words about `super`

Sometimes we need to call methods of parent class to a overridden method of child class. We can achieve this using super function.We can directly use methods of super class or modify them(this is very common).

We could write class B like:

class B(A):

def test(self):

return "B" + super(B, self).test() #super(className,object)

Look at super(B, self) (here, super takes two arguments: a class and its object). This is another approach of calling super .

Difference between super() and super(className, self):

Python 3 encourages using super() , instead of using super(className,self) , both have the same effect. Python 2, only supports the super(className,self) syntax. Since, Python 2 is widely used so Python 3 also has support for this type of super calling. You more about super here .

Introspection

As python supports different types of inheritance so sometimes it needs to be introspected cleanly.

isinstance() : isinstance() takes two argument : an object and a class. It returns True if the given class is anywhere in the inheritance chain of the object’s class.

Output :

True

True

At line 11 we get True because A() is the object of class A .

At line 12 we get True because class A is in the inheritance chain of the class of object B() .

issubclass() : issubclass() takes two argument (class, class).It returns True if the first class contains the second class anywhere in its inheritance chain.

Output :

True

True

Output at line 15 seems okay but output at line 16 may look odd. Clearly, Class A is not a subclass of itself ( Class A ), but Python works in this way!

__bases__() : __bases__() provides a tuple of immediate base classes of a class.

Output:

(<class ‘object’>,)

(<class ‘__main__.A’>,)

(<class ‘object’>,)

(<class ‘__main__.A’>, <class ‘__main__.C’>)

Note : By default, every Python class is the subclass of built-in object class.

__subclasses__() : __subclasses__() returns a list of all

the subclasses a class. Like __bases__() , __subclasses__ only goes one level deep from the class we’re working on.

print(A.__subclasses__())

print(object.__subclasses__())

Output :

__mro__ : __mro__ is an attributes which contains full MRO (Method Resolution Order) of a class as a tuple.

print(D.__mro__)

Output:

(<class ‘__main__.D’>, <class ‘__main__.A’>, <class ‘__main__.C’>, <class ‘object’>)

Another game using __init__ and __new__

Transforming from class to an object is called instantiation. __init__ is used to objects initialization. Inside __init__ function only initialization should be done, nothing more.

This is a sample code using __init__ :

class Person:

def __init__(self, name):

self.name = name



def get_name(self):

return self.name

During object creation __init__ method doesn’t get executed first, the method which gets executed first is __new__ . __new__() method gets most of the same arguments at __init__ and it is responsible for actually creating the new object (prior to initializing object) .

We can use __new__ when we need to control the creation of a new instance.

Let we have a 3 classes, File , TextFile(File) and ImageFile(File) . We need instantiate TextFile for Text files, ImageFile for image file, and File for other type of files. We can obtain this by instantiating them directly.

But what if we could instantiate them only using the parent class ( File ) depending the file content? Yes, we want to instantiate child classes using parent class!

Pretty interesting, NO?

Output :

<__main__.TextFile object at 0x7efc02f1d278>

<__main__.ImageFile object at 0x7efc02f1d2b0>

<__main__.File object at 0x7efc02f1d048>

Here, we are instantiating the TextFile , ImageFile and File classes using the File (parent) class depending on content of file ( file_type ). See differences among outputs of line 35, 36 and 37.

That’s all for today’s game!

Happy gaming with Python!

You can also read :