Python Inheritance and Polymorphism: The Complete Beginner's Guide

Day 12 builds on classes and objects with inheritance, \`super()\`, overriding, polymorphism, abstract base classes, MRO, and mixins - so your code scales without duplication.

Chapter 12 of 20 · Intermediate · 50 min · Python Programming Course

In Day 11 you learned how to create classes that bundle data and behaviour into coherent objects. Every class you wrote started from scratch - defining every attribute and method independently.

But most real-world concepts share structure. Inheritance lets you define shared parts once in a parent class, while child classes add or change only what is unique.

Polymorphism takes this further: one interface, many implementations. Code can work with objects by behavior rather than concrete type.

What You Will Learn in This Chapter

By the end of this tutorial you will be able to:

  • Create child classes that inherit from parent classes
  • Override parent methods with child-specific behaviour
  • Use super() to extend parent behaviour safely
  • Write child __init__ methods correctly
  • Apply single and multiple inheritance
  • Use isinstance() and issubclass()
  • Write polymorphic code across multiple types
  • Create abstract base classes using abc
  • Use mixins for reusable cross-cutting behaviour
  • Choose between inheritance and composition
  • Avoid common inheritance mistakes

Estimated time: 50 minutes reading + 25 minutes practice

The Problem Inheritance Solves

# Without inheritance - duplicated fields and methods
class CreditCardPayment:
    def __init__(self, amount, currency, card_number):
        self.amount = amount
        self.currency = currency
        self.card_number = card_number
        self.status = "pending"

    def validate(self):
        return self.amount > 0

    def get_receipt(self):
        return f"Payment of {self.amount} {self.currency} - {self.status}"
class Payment:
    def __init__(self, amount, currency):
        self.amount = amount
        self.currency = currency
        self.status = "pending"

    def validate(self):
        return self.amount > 0

    def get_receipt(self):
        return f"Payment of {self.amount} {self.currency} - {self.status}"

class CreditCardPayment(Payment):
    def __init__(self, amount, currency, card_number):
        super().__init__(amount, currency)
        self.card_number = card_number

Shared behavior now lives in one place.

Basic Inheritance Syntax

class Parent:
    def greet(self):
        return "Hello from Parent"

class Child(Parent):
    pass

super() - Extending Parent Behaviour

super() delegates to the next class in MRO. Most commonly used in child __init__:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Employee(Person):
    def __init__(self, name, age, company):
        super().__init__(name, age)
        self.company = company

Use super() in regular methods too when you want to extend parent logic.

Method Overriding

Overriding replaces a parent method implementation in a child:

class Shape:
    def area(self):
        return 0

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

Polymorphism - One Interface, Multiple Implementations

class Notification:
    def send(self):
        raise NotImplementedError

class EmailNotification(Notification):
    def __init__(self, message, email):
        self.message = message
        self.email = email
    def send(self):
        return f"Email to {self.email}: {self.message}"

class SMSNotification(Notification):
    def __init__(self, message, phone):
        self.message = message
        self.phone = phone
    def send(self):
        return f"SMS to {self.phone}: {self.message}"

def send_all(notifications):
    for n in notifications:
        print(n.send())

isinstance() and issubclass()

class Animal: pass
class Dog(Animal): pass

dog = Dog()
print(isinstance(dog, Dog))      # True
print(isinstance(dog, Animal))   # True
print(issubclass(Dog, Animal))   # True

Abstract Base Classes

Use abc to enforce required methods:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

A subclass missing an abstract method cannot be instantiated.

Multiple Inheritance and MRO

Python supports multiple inheritance and resolves methods via MRO:

class A:
    def hello(self):
        return "A"

class B(A):
    def hello(self):
        return "B"

class C(A):
    def hello(self):
        return "C"

class D(B, C):
    pass

print(D().__mro__)
print(D().hello())   # B

Mixins

Mixins add reusable capabilities across unrelated classes:

class LogMixin:
    def log(self, message):
        print(f"[{self.__class__.__name__}] {message}")

class ValidationMixin:
    def is_positive(self, value, field_name):
        if value <= 0:
            raise ValueError(f"{field_name} must be positive")

Inheritance vs Composition

  • Use inheritance for true "is-a" relationships.
  • Use composition for "has-a" relationships.

If "Child IS A Parent" sounds wrong, composition usually wins.

A Complete Working Program

Employee system using abstract class + mixin + polymorphism:

from abc import ABC, abstractmethod

class AuditMixin:
    def __init__(self):
        self._audit_log = []
    def _record(self, action):
        self._audit_log.append(action)
    def get_audit_log(self):
        return self._audit_log.copy()

class Employee(AuditMixin, ABC):
    employee_count = 0
    def __init__(self, name, employee_id, base_salary):
        super().__init__()
        self.name = name
        self.employee_id = employee_id
        self.base_salary = base_salary
        self._performance_rating = 3
        Employee.employee_count += 1
        self._record(f"Employee {name} created")

    @abstractmethod
    def calculate_bonus(self):
        pass

class Engineer(Employee):
    def calculate_bonus(self):
        return int(self.base_salary * 0.12)

class Contractor(Employee):
    def calculate_bonus(self):
        return 0

This demonstrates extensible design with shared contracts and type-specific behavior.

5 Inheritance Mistakes Every Beginner Makes

  1. Forgetting super().__init__() in child constructors
  2. Rewriting parent behavior instead of extending with super()
  3. Creating deep, fragile inheritance chains
  4. Using inheritance without a true "is-a" relationship
  5. Forgetting abstract method implementations

Practice: Inheritance and Polymorphism Exercises

  • Vehicle parent with Car/Truck children
  • Abstract Animal class with Dog/Cat/Fish/Bird
  • Person -> Student/Teacher with super()
  • Serialize mixin for Product and Customer
  • Report ABC with SalesReport/InventoryReport/UserReport

-> See all Python OOP exercises with solutions

What Comes Next - Day 13: Modules and Packages

Next chapter covers:

  • Creating and importing modules
  • import, from ... import, as
  • Standard library usage
  • Installing packages with pip
  • Building packages with __init__.py
  • Avoiding circular imports

-> Continue to Day 13: Modules and Packages

Frequently Asked Questions

What is inheritance in Python?

Inheritance allows a child class to automatically receive attributes and methods from a parent class.

What does super() do in Python?

super() resolves and calls parent behavior according to MRO, commonly used for parent initialization.

What is method overriding in Python?

A child class defines a method with the same name as a parent to provide specialized behavior.

What is polymorphism in Python?

The same method call behaves differently across object types that share an interface.

What is an abstract class in Python?

A class that defines required methods via @abstractmethod, enforcing an interface contract.

What is the Method Resolution Order (MRO) in Python?

The order Python uses to search classes for methods/attributes, available via Class.__mro__.

When should I use inheritance vs composition?

Use inheritance for "is-a". Use composition for "has-a".

What is a mixin in Python?

A small class that contributes reusable capability to other classes via inheritance.

Chapter navigation

Frequently asked questions: Inheritance and polymorphism

What is inheritance in Python?

Inheritance allows a child class to automatically receive attributes and methods from a parent class, then extend or override behavior.

What does super() do in Python?

super() returns a proxy to parent behavior according to MRO, commonly used to call parent __init__ and extend parent methods safely.

What is method overriding in Python?

Method overriding happens when a child class defines a method with the same name as the parent, replacing behavior for child instances.

What is polymorphism in Python?

Polymorphism means one interface, many implementations: the same method call works across different object types.

What is an abstract class in Python?

An abstract class, built with ABC and @abstractmethod, defines required methods that subclasses must implement before instantiation.

What is the Method Resolution Order (MRO) in Python?

MRO is the order Python uses to resolve attributes and methods in inheritance hierarchies, especially with multiple inheritance.

When should I use inheritance vs composition?

Use inheritance for true is-a relationships. Use composition for has-a relationships where one class contains or delegates to another.

What is a mixin in Python?

A mixin is a lightweight class that adds reusable capability (like logging or serialization) to unrelated classes via inheritance.