8.1 Polymorphism
Polymorphism is one of the core concepts of Object-Oriented Programming (OOP) that allows objects of different classes to use the same interface.
In Python, polymorphism is achieved through dynamic typing and inheritance. An important aspect of polymorphism is method overloading and method overriding from the base class by the derived class.
Key concepts of polymorphism:
Unified interface for different objects:
Polymorphism allows using a unified interface for objects of different classes that may have different implementations of methods.
Dynamic typing:
In Python, the type of a variable is determined at runtime, which allows functions to work with objects of any class as long as they support the required interface.
Inheritance:
It allows creating class hierarchies where subclasses inherit properties and methods from the base class but can override them for their specific functionality.
Examples of polymorphism:
A simple example of polymorphism: all three classes have a method named move(). This means we can write code that will work with these objects simultaneously.
class Car:
def move(self):
pass
class Human:
def move(self):
pass
class Bird:
def move(self):
print("Caw!")
car = Car()
human = Human()
bird = Bird()
for it in [car, human, bird]:
it.move()
In this case, the "common interface" is the method name, or its signature if the method has parameters.
8.2 Method Overriding
Polymorphism is often used together with inheritance to create class hierarchies where the base class defines a common interface, and subclasses implement specific details.
class Employee:
def get_salary(self):
return 1000
class FullTimeEmployee(Employee):
def get_salary(self):
return 5000
class PartTimeEmployee(Employee):
def get_salary(self):
return 3000
class Intern(Employee):
pass
def print_salary(employee):
print(employee.get_salary())
employees = [FullTimeEmployee(), PartTimeEmployee(), Intern()]
for employee in employees:
print_salary(employee)
This example is similar to the previous one but with an important difference—our classes are not required to have the get_salary method since it is always present in the base class. Now the "common interface" for all classes is not just the get_salary() method, but the Employee class with all its methods and attributes.
8.3 Calling Derived Class Methods
Take a close look at the code below:
We call the print_salary() method that is only in the base class Employee, and it calls another method of the base class—get_salary(). So, which salaries will be displayed?
class Employee:
def print_salary(self):
salary = self.get_salary()
print(salary)
def get_salary(self):
return 1000
class FullTimeEmployee(Employee):
def get_salary(self):
return 5000
class PartTimeEmployee(Employee):
def get_salary(self):
return 3000
class Intern(Employee):
pass
employees = [FullTimeEmployee(), PartTimeEmployee(), Intern()]
for employee in employees:
employee.print_salary()
Important! Read what's written below—it's important.
If we call the print_salary() method, for example, for the FullTimeEmployee class, it will call the method of the base class since the class itself does not declare such a method.
But this print_salary() method will call the get_salary() method on the self object, which is of type FullTimeEmployee, not Employee. So the get_salary() method from the FullTimeEmployee class will be called!
Moreover, the FullTimeEmployee class can call the get_salary() method of the base class for its own needs. For example, if we want to calculate the salary as a percentage of the base rate:
class FullTimeEmployee(Employee):
def get_salary(self):
base_salary = super().get_salary()
return base_salary * 5
# 500%
class PartTimeEmployee(Employee):
def get_salary(self):
base_salary = super().get_salary()
return base_salary * 3 # 300%
8.4 Method Overloading
Method overloading in Python is the ability to create multiple methods with the same name but different parameters. However, pure method overloading is not supported in Python as it is in other languages (like C++ or Java).
In Python, you can simulate method overloading by using default arguments, *args, and **kwargs.
Examples of method overloading
We can implement a function that will have different behavior depending on the number of arguments passed. This can be done in various ways, for example:
class Example:
def display(self, a=None, b=None):
if a is not None and b is not None: print(a, b)
elif a is not None: print(a)
else: print("No arguments")
obj = Example()
obj.display(1, 2) # Output: 1 2
obj.display(1) # Output: 1
obj.display() # Output: No arguments
You can also consider the type of the passed data—just do a type check:
class Example:
def mod(self, a, b):
if type(a) == int and type(b) == int: print(a % b)
elif type(a) == float or type(b) == float: print(round(a / b))
else: print("Help instruction: a and b should be int or float")
obj = Example()
obj.mod(5, 2) # Output: 1
obj.mod(5.0, 2) # Output: 2
obj.mod("5", 2) # Output: Help instruction: a and b should be int or float
If the wrong data types are passed, you can display instructions or even a link to documentation online—this is also a very popular solution.
GO TO FULL VERSION