Python Inheritance

Learn about inheritance in Python - how to create new classes based on existing classes.

Python Inheritance

Inheritance allows us to define a class that inherits all the methods and properties from another class.

Parent class is the class being inherited from, also called base class.

Child class is the class that inherits from another class, also called derived class.

Create a Parent Class

Any class can be a parent class, so the syntax is the same as creating any other class:

Example - Create a class named Person, with firstname and lastname properties, and a printname method:

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname

    def printname(self):
        print(self.firstname, self.lastname)

# Use the Person class to create an object, and then execute the printname method:
x = Person("John", "Doe")
x.printname()

Create a Child Class

To create a class that inherits the functionality from another class, send the parent class as a parameter when creating the child class:

Example - Create a class named Student, which will inherit the properties and methods from the Person class:

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname

    def printname(self):
        print(self.firstname, self.lastname)

class Student(Person):
    pass

x = Student("Mike", "Olsen")
x.printname()

Note: Use the pass keyword when you do not want to add any other properties or methods to the class.

Now the Student class has the same properties and methods as the Person class.

Add the __init__() Function

So far we have created a child class that inherits the properties and methods from its parent.

We want to add the __init__() function to the child class (instead of the pass keyword).

Note: The __init__() function is called automatically every time the class is being used to create a new object.

Example - Add the __init__() function to the Student class:

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname

    def printname(self):
        print(self.firstname, self.lastname)

class Student(Person):
    def __init__(self, fname, lname):
        # add properties etc.

x = Student("Mike", "Olsen")

When you add the __init__() function, the child class will no longer inherit the parent's __init__() function.

Note: The child's __init__() function overrides the inheritance of the parent's __init__() function.

To keep the inheritance of the parent's __init__() function, add a call to the parent's __init__() function:

Example

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname

    def printname(self):
        print(self.firstname, self.lastname)

class Student(Person):
    def __init__(self, fname, lname):
        Person.__init__(self, fname, lname)

x = Student("Mike", "Olsen")
x.printname()

Now we have successfully added the __init__() function, and kept the inheritance of the parent class, and we are ready to add functionality in the __init__() function.

Use the super() Function

Python also has a super() function that will make the child class inherit all the methods and properties from its parent:

Example

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname

    def printname(self):
        print(self.firstname, self.lastname)

class Student(Person):
    def __init__(self, fname, lname):
        super().__init__(fname, lname)

x = Student("Mike", "Olsen")
x.printname()

By using the super() function, you do not have to use the name of the parent element, it will automatically inherit the methods and properties from its parent.

Add Properties

Example - Add a property called graduationyear to the Student class:

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname

    def printname(self):
        print(self.firstname, self.lastname)

class Student(Person):
    def __init__(self, fname, lname):
        super().__init__(fname, lname)
        self.graduationyear = 2019

x = Student("Mike", "Olsen")
print(x.graduationyear)

In the example below, the year 2019 should be a variable, and passed into the Student class when creating student objects. To do so, add another parameter in the __init__() function:

Example - Add a year parameter, and pass the correct year when creating objects:

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname

    def printname(self):
        print(self.firstname, self.lastname)

class Student(Person):
    def __init__(self, fname, lname, year):
        super().__init__(fname, lname)
        self.graduationyear = year

x = Student("Mike", "Olsen", 2019)
print(x.graduationyear)

Add Methods

Example - Add a method called welcome to the Student class:

class Person:
    def __init__(self, fname, lname):
        self.firstname = fname
        self.lastname = lname

    def printname(self):
        print(self.firstname, self.lastname)

class Student(Person):
    def __init__(self, fname, lname, year):
        super().__init__(fname, lname)
        self.graduationyear = year

    def welcome(self):
        print("Welcome", self.firstname, self.lastname, "to the class of", self.graduationyear)

x = Student("Mike", "Olsen", 2019)
x.welcome()

If you add a method in the child class with the same name as a function in the parent class, the inheritance of the parent method will be overridden.

Method Overriding

Child classes can override methods from their parent classes:

Example - Method overriding:

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} makes a sound"
    
    def move(self):
        return f"{self.name} moves"

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed
    
    def speak(self):  # Override parent method
        return f"{self.name} barks"
    
    def fetch(self):  # New method specific to Dog
        return f"{self.name} fetches the ball"

class Cat(Animal):
    def __init__(self, name, color):
        super().__init__(name)
        self.color = color
    
    def speak(self):  # Override parent method
        return f"{self.name} meows"
    
    def climb(self):  # New method specific to Cat
        return f"{self.name} climbs the tree"

# Usage
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers", "Orange")

print(dog.speak())    # Buddy barks
print(dog.move())     # Buddy moves (inherited)
print(dog.fetch())    # Buddy fetches the ball

print(cat.speak())    # Whiskers meows
print(cat.climb())    # Whiskers climbs the tree

Multiple Inheritance

Python supports multiple inheritance, where a class can inherit from multiple parent classes:

Example - Multiple inheritance:

class Mammal:
    def __init__(self, name):
        self.name = name
        self.warm_blooded = True
    
    def breathe(self):
        return f"{self.name} breathes air"

class Carnivore:
    def __init__(self):
        self.diet = "meat"
    
    def hunt(self):
        return f"Hunting for prey"

class Lion(Mammal, Carnivore):
    def __init__(self, name, pride_size):
        Mammal.__init__(self, name)
        Carnivore.__init__(self)
        self.pride_size = pride_size
    
    def roar(self):
        return f"{self.name} roars loudly"
    
    def hunt(self):  # Override Carnivore's hunt method
        return f"{self.name} hunts with the pride of {self.pride_size}"

# Usage
lion = Lion("Simba", 8)
print(lion.breathe())       # Simba breathes air
print(lion.hunt())          # Simba hunts with the pride of 8
print(lion.roar())          # Simba roars loudly
print(f"Diet: {lion.diet}") # Diet: meat

Method Resolution Order (MRO)

When using multiple inheritance, Python uses Method Resolution Order to determine which method to call:

Example - Understanding MRO:

class A:
    def method(self):
        return "Method from A"

class B(A):
    def method(self):
        return "Method from B"

class C(A):
    def method(self):
        return "Method from C"

class D(B, C):
    pass

# Check the Method Resolution Order
print(D.__mro__)
# (, , , , )

d = D()
print(d.method())  # Method from B (B comes before C in MRO)

# You can also check MRO with mro() method
print(D.mro())

Abstract Base Classes

Use abstract base classes to define interfaces that subclasses must implement:

Example - Abstract base class:

from abc import ABC, abstractmethod

class Vehicle(ABC):
    def __init__(self, make, model):
        self.make = make
        self.model = model
    
    @abstractmethod
    def start_engine(self):
        pass
    
    @abstractmethod
    def stop_engine(self):
        pass
    
    def get_info(self):
        return f"{self.make} {self.model}"

class Car(Vehicle):
    def __init__(self, make, model, doors):
        super().__init__(make, model)
        self.doors = doors
    
    def start_engine(self):
        return f"{self.make} {self.model} engine started with key"
    
    def stop_engine(self):
        return f"{self.make} {self.model} engine stopped"

class Motorcycle(Vehicle):
    def __init__(self, make, model, engine_size):
        super().__init__(make, model)
        self.engine_size = engine_size
    
    def start_engine(self):
        return f"{self.make} {self.model} engine started with button"
    
    def stop_engine(self):
        return f"{self.make} {self.model} engine stopped"

# Usage
car = Car("Toyota", "Camry", 4)
bike = Motorcycle("Honda", "CBR", 600)

print(car.get_info())        # Toyota Camry
print(car.start_engine())    # Toyota Camry engine started with key
print(bike.start_engine())   # Honda CBR engine started with button

# This would raise an error:
# vehicle = Vehicle("Generic", "Vehicle")  # TypeError: Can't instantiate abstract class

Practical Example: Employee Management System

Complete inheritance example:

from abc import ABC, abstractmethod
from datetime import datetime

class Employee(ABC):
    def __init__(self, employee_id, name, email):
        self.employee_id = employee_id
        self.name = name
        self.email = email
        self.hire_date = datetime.now()
    
    @abstractmethod
    def calculate_salary(self):
        pass
    
    def get_info(self):
        return f"ID: {self.employee_id}, Name: {self.name}, Email: {self.email}"
    
    def years_of_service(self):
        return (datetime.now() - self.hire_date).days // 365

class FullTimeEmployee(Employee):
    def __init__(self, employee_id, name, email, annual_salary):
        super().__init__(employee_id, name, email)
        self.annual_salary = annual_salary
    
    def calculate_salary(self):
        return self.annual_salary / 12  # Monthly salary

class PartTimeEmployee(Employee):
    def __init__(self, employee_id, name, email, hourly_rate, hours_per_week):
        super().__init__(employee_id, name, email)
        self.hourly_rate = hourly_rate
        self.hours_per_week = hours_per_week
    
    def calculate_salary(self):
        return self.hourly_rate * self.hours_per_week * 4  # Monthly salary

class Manager(FullTimeEmployee):
    def __init__(self, employee_id, name, email, annual_salary, team_size):
        super().__init__(employee_id, name, email, annual_salary)
        self.team_size = team_size
        self.team_members = []
    
    def add_team_member(self, employee):
        self.team_members.append(employee)
    
    def calculate_salary(self):
        base_salary = super().calculate_salary()
        bonus = self.team_size * 500  # $500 bonus per team member
        return base_salary + bonus
    
    def get_team_info(self):
        team_info = f"Manager: {self.name}\nTeam Size: {self.team_size}\nTeam Members:\n"
        for member in self.team_members:
            team_info += f"  - {member.name}\n"
        return team_info

class Contractor(Employee):
    def __init__(self, employee_id, name, email, project_rate, contract_duration):
        super().__init__(employee_id, name, email)
        self.project_rate = project_rate
        self.contract_duration = contract_duration  # in months
    
    def calculate_salary(self):
        return self.project_rate / self.contract_duration

# Usage
# Create employees
ft_employee = FullTimeEmployee("FT001", "Alice Johnson", "alice@company.com", 60000)
pt_employee = PartTimeEmployee("PT001", "Bob Smith", "bob@company.com", 25, 20)
manager = Manager("MG001", "Carol Davis", "carol@company.com", 80000, 3)
contractor = Contractor("CT001", "David Wilson", "david@company.com", 15000, 6)

# Add team members to manager
manager.add_team_member(ft_employee)
manager.add_team_member(pt_employee)

# Display information
employees = [ft_employee, pt_employee, manager, contractor]

for emp in employees:
    print(f"\n{emp.get_info()}")
    print(f"Monthly Salary: ${emp.calculate_salary():.2f}")
    print(f"Years of Service: {emp.years_of_service()}")
    
    if isinstance(emp, Manager):
        print(emp.get_team_info())