What is inheritance in Python
Inheritance is an OOP mechanism that allows you to create a new class based on an existing one. The child class automatically receives all the attributes and methods of the parent class, and can also add its own or override inherited ones.
Inheritance syntax
class BaseClass:
def __init__(self, base_attr):
self.base_attr = base_attr
def base_method(self):
print("Method from BaseClass")
class DerivedClass(BaseClass):
def __init__(self, base_attr, derived_attr):
super().__init__(base_attr) # Calling the parent constructor
self.derived_attr = derived_attr
def derived_method(self):
print("Method from DerivedClass")
# Creating a child class object
derived = DerivedClass("Base Attribute", "Derived Attribute")
derived.base_method() # Output: Method from BaseClass
derived.derived_method() # Output: Method from DerivedClass
super() function in Python
The super() function provides access to the methods of the parent class, which is especially important when redefining methods.:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def get_info(self):
return f"Employee: {self.name}, Salary: {self.salary}"
class Manager(Employee):
def __init__(self, name, salary, department):
super().__init__(name, salary) # Calling the parent constructor
self.department = department
def get_info(self):
base_info = super().get_info() # Getting information from the parent
return f"{base_info}, Department: {self.department}"
manager = Manager("John", 50000, "IT")
print(manager.get_info()) # Employee: John, Salary: 50000, Department: IT
Polymorphism in Python
Polymorphism is the ability of objects of different classes to respond to the same method calls in different ways. In Python, polymorphism is implemented through redefinition of methods.
Basic example of polymorphism
class Animal:
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
return "Woof!"
class Cat(Animal):
def make_sound(self):
return "Meow!"
class Bird(Animal):
def make_sound(self):
return "Tweet!"
# Polymorphic use
of animals = [Dog(), Cat(), Bird()]
for animal in animals:
print(animal.make_sound())
# Conclusion:
# Woof!
# Meow!
# Tweet!
A practical example: a payment system
class PaymentMethod:
def process_payment(self, amount):
raise NotImplementedError("Subclass must implement")
class CreditCard(PaymentMethod):
def __init__(self, card_number):
self.card_number = card_number
def process_payment(self, amount):
return f"Processing ${amount} via Credit Card ending in {self.card_number[-4:]}"
class PayPal(PaymentMethod):
def __init__(self, email):
self.email = email
def process_payment(self, amount):
return f"Processing ${amount} via PayPal account {self.email}"
class BankTransfer(PaymentMethod):
def __init__(self, account_number):
self.account_number = account_number
def process_payment(self, amount):
return f"Processing ${amount} via Bank Transfer to {self.account_number}"
# Function for processing a payment in any way
def make_payment(payment_method, amount):
return payment_method.process_payment(amount)
# Using
credit_card = CreditCard("1234567890123456")
paypal = PayPal("user@example.com ")
bank_transfer = BankTransfer("ACC123456789")
print(make_payment(credit_card, 100))
print(make_payment(paypal, 50))
print(make_payment(bank_transfer, 200))
Advanced examples of inheritance and polymorphism
An example with geometric shapes
import math
class Shape:
def __init__(self, name):
self.name = name
def area(self):
raise NotImplementedError("Subclass must implement area method")
def perimeter(self):
raise NotImplementedError("Subclass must implement perimeter method")
def __str__(self):
return f"{self.name} - Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"
class Circle(Shape):
def __init__(self, radius):
super().__init__("Circle")
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__("Rectangle")
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Triangle(Shape):
def __init__(self, a, b, c):
super().__init__("Triangle")
self.a = a
self.b = b
self.c = c
def area(self):
s = self.perimeter() / 2
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self):
return self.a + self.b + self.c
# Creating
a shapes collection = [
Circle(5),
Rectangle(4, 6),
Triangle(3, 4, 5)
]
# Polymorphic usage
for shape in shapes:
print(shape)
Multiple inheritance
Python supports multiple inheritance - the ability to inherit from multiple classes:
class Flyable:
def fly(self):
return "Flying through the air"
class Swimmable:
def swim(self):
return "Swimming in water"
class Duck(Animal, Flyable, Swimmable):
def make_sound(self):
return "Quack!"
duck = Duck()
print(duck.make_sound()) # Quack!
print(duck.fly()) # Flying through the air
print(duck.swim()) # Swimming in water
Abstract classes and methods
Abstract classes are used to create a stricter class hierarchy.:
from abc import ABC, abstractmethod
class Vehicle(ABC):
def __init__(self, brand, model):
self.brand = brand
self.model = model
@abstractmethod
def start_engine(self):
pass
@abstractmethod
def stop_engine(self):
pass
def get_info(self):
return f"{self.brand} {self.model}"
class Car(Vehicle):
def start_engine(self):
return "Car engine started"
def stop_engine(self):
return "Car engine stopped"
class Motorcycle(Vehicle):
def start_engine(self):
return "Motorcycle engine started"
def stop_engine(self):
return "Motorcycle engine stopped"
# Creating objects
car = Car("Toyota", "Camry")
motorcycle = Motorcycle("Honda", "CBR")
print(car.get_info()) # Toyota Camry
print(car.start_engine()) # Car engine started
print(motorcycle.start_engine()) # Motorcycle engine started
Advantages of inheritance and polymorphism
Main advantages:
Code reuse: Avoiding code duplication by inheriting common functionality.
Extensibility: Easily adding new classes without changing existing code.
Polymorphism: A single interface for working with objects of different types.
Encapsulation: Hiding implementation details and providing a simple interface.
Modularity: Separation of code into logical components.
The principle of substitution of Liskov
An important principle of OOP: superclass objects should be replaced by subclass objects without disrupting the program:
def process_shapes(shapes_list):
total_area = 0
for shape in shapes_list:
total_area += shape.area() # Works for any Shape subclass
return total_area
# The function works with any inheritors of Shape
mixed_shapes = [Circle(3), Rectangle(2, 4), Triangle(3, 4, 5)]
total = process_shapes(mixed_shapes)
print(f"Total area: {total:.2f}")
Best Practices
1. Use composition instead of inheritance when appropriate
class Engine:
def __init__(self, power):
self.power = power
def start(self):
return "Engine started"
class Car:
def __init__(self, brand, engine):
self.brand = brand
self.engine = engine # Composition instead of inheritance
def start(self):
return f"{self.brand}: {self.engine.start()}"
2. Redefine methods meaningfully
class Animal:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Animal: {self.name}"
def __repr__(self):
return f"Animal('{self.name}')"
class Dog(Animal):
def __str__(self):
return f"Dog: {self.name}"
def __repr__(self):
return f"Dog('{self.name}')"
3. Use isinstance() for type checking
def handle_animal(animal):
if isinstance(animal, Dog):
return f"Walking the dog {animal.name}"
elif isinstance(animal, Cat):
return f"Feeding the cat {animal.name}"
else:
return f"Caring for {animal.name}"
Inheritance and polymorphism are powerful Python tools that make code more organized, flexible, and easy to maintain. Using these concepts correctly allows you to create scalable and maintainable applications.