Playing with inheritance in Python
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 returnsTrue
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 returnsTrue
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 :
This story is published in Noteworthy, where 10,000+ readers come every day to learn about the people & ideas shaping the products we love.
Follow our publication to see more product & design stories featured by the Journal team.