Inheritance is one of the most powerful features of object-oriented programming. It allows you to create new classes based on existing ones, inheriting their attributes and methods while adding or modifying functionality. This promotes code reuse and helps you build logical class hierarchies.
Polymorphism allows objects of different classes to be used interchangeably when they share a common interface. Together, inheritance and polymorphism enable you to write flexible, extensible code. By the end of this lesson, you'll understand how to create class hierarchies and leverage polymorphism in your programs.
What You'll Learn
- Creating subclasses
- Method overriding
- The super() function
- Understanding inheritance hierarchies
- Polymorphism in Python
- Best practices for inheritance
Basic Inheritance
Inheritance lets you create a new class (child/subclass) that inherits from an existing class (parent/superclass):
class Animal:
"""Base class for all animals."""
def __init__(self, name, species):
self.name = name
self.species = species
def make_sound(self):
return "Some generic animal sound"
def get_info(self):
return f"{self.name} is a {self.species}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
def make_sound(self):
return "Woof!"
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
def make_sound(self):
return "Meow!"
# Using the classes
dog = Dog("Buddy", "Canine")
cat = Cat("Whiskers", "Feline")
print(dog.get_info()) # Buddy is a Canine (inherited method)
print(dog.make_sound()) # Woof! (overridden method)
print(cat.make_sound()) # Meow! (overridden method)
The child class automatically inherits all methods and attributes from the parent.
Method Overriding
You can override parent methods by defining them again in the child class:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
return "Vehicle started"
def get_info(self):
return f"{self.year} {self.make} {self.model}"
class Car(Vehicle):
def start(self):
return "Car engine started with a key"
class ElectricCar(Vehicle):
def start(self):
return "Electric car started silently"
# Each class has its own start() behavior
car = Car("Toyota", "Camry", 2020)
electric = ElectricCar("Tesla", "Model 3", 2021)
print(car.start()) # Car engine started with a key
print(electric.start()) # Electric car started silently
Using super()
The super() function gives you access to the parent class. It's essential for calling parent methods and constructors:
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def make_sound(self):
return "Some sound"
class Dog(Animal):
def __init__(self, name, breed):
# Call parent __init__ using super()
super().__init__(name, "Canine")
self.breed = breed
def make_sound(self):
# Call parent method and extend it
parent_sound = super().make_sound()
return f"{parent_sound} - Woof!"
dog = Dog("Buddy", "Golden Retriever")
print(dog.name) # Buddy (from parent)
print(dog.species) # Canine (from parent)
print(dog.breed) # Golden Retriever (from child)
print(dog.make_sound()) # Some sound - Woof!
Multi-level Inheritance
You can create inheritance chains with multiple levels:
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} is eating"
class Mammal(Animal):
def __init__(self, name, has_fur=True):
super().__init__(name)
self.has_fur = has_fur
def breathe(self):
return f"{self.name} is breathing air"
class Dog(Mammal):
def __init__(self, name, breed):
super().__init__(name, has_fur=True)
self.breed = breed
def bark(self):
return f"{self.name} is barking"
# Dog inherits from Mammal, which inherits from Animal
dog = Dog("Buddy", "Golden Retriever")
print(dog.eat()) # Buddy is eating (from Animal)
print(dog.breathe()) # Buddy is breathing air (from Mammal)
print(dog.bark()) # Buddy is barking (from Dog)
Polymorphism
Polymorphism allows objects of different classes to be treated uniformly when they share a common interface:
class Shape:
def area(self):
raise NotImplementedError("Subclass must implement area()")
def perimeter(self):
raise NotImplementedError("Subclass must implement perimeter()")
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
# Polymorphism in action
shapes = [
Rectangle(5, 3),
Circle(4),
Rectangle(2, 2)
]
# All shapes can be treated the same way
for shape in shapes:
print(f"Area: {shape.area():.2f}, Perimeter: {shape.perimeter():.2f}")
Practical Examples
Here are comprehensive examples combining inheritance and polymorphism:
# Example 1: Employee hierarchy
class Employee:
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
def calculate_salary(self):
raise NotImplementedError("Subclass must implement calculate_salary()")
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"
class FullTimeEmployee(Employee):
def __init__(self, name, employee_id, monthly_salary):
super().__init__(name, employee_id)
self.monthly_salary = monthly_salary
def calculate_salary(self):
return self.monthly_salary
class PartTimeEmployee(Employee):
def __init__(self, name, employee_id, hourly_rate, hours_worked):
super().__init__(name, employee_id)
self.hourly_rate = hourly_rate
self.hours_worked = hours_worked
def calculate_salary(self):
return self.hourly_rate * self.hours_worked
# Polymorphism: treat all employees the same
employees = [
FullTimeEmployee("Alice", "E001", 5000),
PartTimeEmployee("Bob", "E002", 25, 80),
FullTimeEmployee("Charlie", "E003", 6000)
]
for emp in employees:
print(f"{emp.get_info()}: ${emp.calculate_salary()}")
# Example 2: Payment methods
class PaymentMethod:
def process_payment(self, amount):
raise NotImplementedError("Subclass must implement process_payment()")
class CreditCard(PaymentMethod):
def __init__(self, card_number):
self.card_number = card_number
def process_payment(self, amount):
return f"Processing ${amount} with 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}"
# All payment methods work the same way
def checkout(payment_method, amount):
return payment_method.process_payment(amount)
card = CreditCard("1234567890123456")
paypal = PayPal("user@example.com")
bank = BankTransfer("ACC123456")
print(checkout(card, 100)) # Works with any payment method
print(checkout(paypal, 200))
print(checkout(bank, 300))
Method Resolution Order (MRO)
Python uses Method Resolution Order to determine which method to call in complex inheritance:
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B"
class C(A):
def method(self):
return "C"
class D(B, C):
pass
d = D()
print(d.method()) # "B" (first in inheritance list)
print(D.__mro__) # Shows method resolution order
Try It Yourself
Practice inheritance and polymorphism:
-
Vehicle Hierarchy: Create a
Vehiclebase class and subclassesCar,Motorcycle, andTruck. Each should override methods appropriately. -
Media Library: Create a
Mediabase class with subclassesBook,Movie, andMusic. Use polymorphism to display information about all media items. -
Bank Accounts: Create a
BankAccountbase class withCheckingAccountandSavingsAccountsubclasses that have different interest rates and withdrawal rules. -
Shape Calculator: Create a
Shapebase class withTriangle,Square, andCirclesubclasses. Use polymorphism to calculate total area of a list of shapes. -
Animal Zoo: Create an
Animalbase class with multiple subclasses. Implement aZooclass that can feed and interact with all animals polymorphically.
Summary
Inheritance and polymorphism are powerful OOP features that promote code reuse and flexibility. Inheritance lets you create new classes based on existing ones, inheriting their functionality. Method overriding allows customization, and super() provides access to parent classes. Polymorphism enables treating different objects uniformly when they share a common interface.
These concepts are fundamental to object-oriented design and appear throughout Python's standard library and popular frameworks. Mastering them will help you write more maintainable, extensible code.
What's Next?
In the next lesson, we'll explore special methods (magic methods) that let you customize how objects behave. You'll learn about __str__, __repr__, operator overloading, and context managers. These features make your classes more Pythonic and intuitive to use.