Inheritance and polymorphism are two fundamental concepts in object-oriented programming (OOP) that allow code reuse and flexibility in Python. Inheritance enables a new class to acquire attributes and behaviors from an existing class, called the parent or base class. This helps reduce code duplication by factoring out common capabilities into superclass. Polymorphism allows objects with different types to respond to the same method invocation dynamically at runtime based on their class.
In this comprehensive guide, we will explore practical techniques for leveraging inheritance hierarchies and polymorphism in Python through examples and hands-on exercises. Readers will learn how to:
Table of Contents
Open Table of Contents
Implementing Single Inheritance
Single inheritance allows a Python class to inherit attributes and behaviors from one parent class. The syntax is simple - include the parent class name in parentheses when defining the new child class:
class ParentClass:
# parent class definition
class ChildClass(ParentClass):
# child class inherits from ParentClass
For example, we can create an Animal
parent class with shared attributes like name
and age
. The child Cat
class inherits the __init__()
constructor allowing it to initialize name
and age
:
class Animal:
"""Parent class with shared attributes"""
def __init__(self, name, age):
self.name = name
self.age = age
class Cat(Animal):
"""Child class inherits Animal's __init__()"""
def __init__(self, name, age):
super().__init__(name, age) # call parent __init__()
felix = Cat("Felix", 3)
print(felix.name) # "Felix"
print(felix.age) # 3
The super()
function gives access to the parent class allowing the Cat
constructor to leverage reuse code in Animal
. This demonstrates the code reuse benefits of single inheritance.
Let’s practice creating a single inheritance hierarchy:
Exercise: Implement a Vehicle
parent class with num_wheels
and color
attributes. Create a child Bike
class that inherits from it. Initialize Bike
instances with a different default num_wheels
value. Verify inheritance is working properly by printing the attributes.
Extending Parent Classes with Overrides
A key benefit of inheritance is that child classes can override parent class methods and attributes to specialize behavior.
To override a parent’s method, redefine the method with the same name in the child class:
class Parent:
def method(self):
print("Parent method")
class Child(Parent):
def method(self):
print("Child method") # overrides parent
child = Child()
child.method() # "Child method"
Attributes can be similarly overridden by assigning values in the child class __init__()
method:
class Parent:
def __init__(self):
self.attr = 1
class Child(Parent):
def __init__(self):
super().__init__()
self.attr = 2 # overrides parent attr
child = Child()
print(child.attr) # 2
Let’s override a parent class method and attribute:
Exercise:
- Define a
Shape
parent class with asides
attribute initialized to 0. - Create a
Square
child class that inheritsShape
and setssides
to 4 in its constructor. - Override
Shape
’sarea()
method inSquare
to return the area of a square. - Print the
area()
andsides
to verify overrides.
Leveraging Multiple Inheritance
Python supports inheriting from multiple parent classes through multiple inheritance. This provides even more code reuse flexibility.
The child class specifies all parent classes separated by commas:
class Parent1:
# parent 1 definition
class Parent2:
# parent 2 definition
class Child(Parent1, Parent2):
# inherits from multiple parents
The first parent listed has higher precedence if attributes conflict.
Let’s see an example of multiple inheritance combining SwimFish
and FlyBird
Mixins with a core FlyingFish
class:
class SwimFish:
def swim(self):
print("Swimming!")
class FlyBird:
def fly(self):
print("Flying!")
class FlyingFish(SwimFish, FlyBird):
pass # inherits swim() and fly()
ff = FlyingFish()
ff.swim() # Swimming!
ff.fly() # Flying!
This allows mixing reusable swimming and flying behaviors through multiple inheritance.
Let’s practice multiple inheritance by combining mixing classes:
Exercise:
- Create
SpeakAnimal
andRollRobot
parent classes withspeak()
androll()
methods that print appropriate messages. - Inherit from both parent classes in a
SpeakingRobot
child class. - Create an instance and call its inherited methods to test it out.
Using Abstract Base Classes for Polymorphism
Python’s powerful polymorphism allows treating derived classes as their parent type seamlessly. This flexibility is crucial for things like containers holding elements of differing types.
We can define abstract base classes (ABCs) that specify interfaces for polymorphism using the abc
module:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def sides(self):
pass
Any child class must implement the abstract methods to work properly:
class Square(Shape):
def __init__(self, length):
self.length = length
def area(self):
return self.length ** 2
def sides(self):
return 4
square = Square(5)
print(square.area()) # 25
Now we can treat child classes polymorphically through the base class type:
shapes = [Square(4), Circle(3)]
for shape in shapes:
print(shape.area())
This iterates through each shape treating them interchangeably based on their Shape
type.
Let’s make an abstract class and subclasses:
Exercise:
- Define an abstract
Animal
base class with abstractspeak()
method. - Create
Cat
andDog
subclasses implementingspeak()
to print an appropriate message. - Store instances of each animal in a list and call
speak()
on each polymorphically.
Leveraging Duck Typing for Flexibility
Python also provides flexibility through ‘duck typing’ - the idea that if it walks and quacks like a duck, we can treat it as a duck! This allows polymorphism between classes without inheriting from a common base.
As long as the needed methods and attributes are present, Python treats unrelated objects similarly:
class Duck:
def quack(self):
print("Quack!")
class Radio:
def quack(self):
print("Quack sounds!")
def make_quack(obj):
obj.quack()
duck = Duck()
radio = Radio()
make_quack(duck) # Quack!
make_quack(radio) # Quack sounds!
Even though Radio
is not a Duck
subclass, make_quack()
can treat it like a duck since it has the expected quack()
method. This dynamic flexibility enables loose coupling and abstraction in Python.
Let’s practice some duck typing:
Exercise:
- Create a
Quacker
class with aquack()
method that prints a quack message. - Define unrelated
Robot
andCat
classes with matchingquack()
methods. - Pass all three classes into a
let_quack()
function that callsquack()
.
Summary
In this comprehensive guide, we explored practical techniques for implementing inheritance hierarchies and polymorphic behavior in Python:
- Single and multiple inheritance allow flexible code reuse in class hierarchies
- Methods and attributes can be overridden in subclasses to specialize behaviors
super()
gives access to parent implementations from the child class- Abstract base classes define interfaces for polymorphism
- Duck typing provides dynamic polymorphism between unrelated classes
Through hands-on examples and exercises, readers should now feel confident leveraging inheritance and polymorphism to write cleaner, more flexible, and reusable object-oriented code in Python. Mastering these core principles opens the door to effectively modeling complex domains and implementing robust systems in Python.
Conclusion
Inheritance and polymorphism are foundational pillars of object-oriented programming in Python. This guide provided a practical introduction through concrete examples and hands-on exercises for implementing inheritance hierarchies and enabling polymorphic behavior. Readers should now have a solid grasp of utilizing these techniques to produce more modular and maintainable code.
There are many additional aspects to explore such as multiple inheritance quirks, metaclass programming, abstract class registries, and optimized dispatch approaches. Start by applying the basics covered here. Get creative extending reusable parent classes through inheritance and take advantage of polymorphism for dynamic flexibility. Learn to leverage these core OOP concepts and you’ll be on your way to mastering idiomatic class design in Python!