Inheritance and Polymorphism

Daniel Sarney
Python Intermediate

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:

  1. Vehicle Hierarchy: Create a Vehicle base class and subclasses Car, Motorcycle, and Truck. Each should override methods appropriately.

  2. Media Library: Create a Media base class with subclasses Book, Movie, and Music. Use polymorphism to display information about all media items.

  3. Bank Accounts: Create a BankAccount base class with CheckingAccount and SavingsAccount subclasses that have different interest rates and withdrawal rules.

  4. Shape Calculator: Create a Shape base class with Triangle, Square, and Circle subclasses. Use polymorphism to calculate total area of a list of shapes.

  5. Animal Zoo: Create an Animal base class with multiple subclasses. Implement a Zoo class 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.

Video Tutorial Coming Soon

The video tutorial for this lesson will be available soon. Check back later!

Continue Learning

12 lessons
Python For Beginners

Start your learning journey with Python For Beginners. This course includes comprehensive lessons covering everything you need to know.