Web Analytics

Inheritance

Intermediate ~30 min read

Inheritance is a fundamental OOP concept that allows you to create new classes based on existing ones. The child class inherits attributes and methods from the parent class, enabling code reuse and establishing logical hierarchies between related classes.

Key Terms

  • Parent class (base class, superclass): The class being inherited from
  • Child class (derived class, subclass): The class that inherits
  • super(): Function to call parent class methods
  • Override: Redefining a parent method in the child

Basic Inheritance

To create a child class, put the parent class name in parentheses after the class name. The child automatically inherits all attributes and methods from the parent.

Output
Click Run to execute your code

Use isinstance() for Type Checking

With inheritance, isinstance(dog, Animal) returns True because Dog inherits from Animal. This is better than type(dog) == Dog which doesn't account for inheritance.

The super() Function

When a child class defines its own __init__, it overrides the parent's. Use super() to call the parent's __init__ and ensure proper initialization.

Output
Click Run to execute your code

Always Call super().__init__() First

If your child class has its own __init__, call super().__init__() at the beginning to properly initialize the parent's attributes. Forgetting this is a common source of AttributeError bugs.

Method Overriding

Child classes can override parent methods by defining a method with the same name. You can either replace the method entirely or extend it by calling the parent's version.

Output
Click Run to execute your code

Extend vs Replace

  • Replace: Define method without calling super() - completely new behavior
  • Extend: Call super().method() inside your override - add to parent behavior

Extending is often preferred because it preserves parent functionality while adding child-specific behavior.

Multiple Inheritance

Python supports inheriting from multiple parent classes. While powerful, this requires understanding the Method Resolution Order (MRO) to know which method gets called.

Output
Click Run to execute your code

Prefer Mixins or Composition

Instead of complex multiple inheritance, consider using mixins (small classes with focused functionality) or composition (having objects as attributes rather than inheriting). These patterns are easier to understand and maintain.

Common Mistakes

1. Forgetting to call super().__init__()

# Wrong - parent attributes not initialized
class Student(Person):
    def __init__(self, name, age, grade):
        self.grade = grade  # name and age are missing!

# Correct - call super() first
class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self.grade = grade

2. Hardcoding parent class name

# Bad - hardcodes parent class name
class Child(Parent):
    def method(self):
        Parent.method(self)  # What if parent changes?

# Good - use super() for flexibility
class Child(Parent):
    def method(self):
        super().method()  # Works with any parent

3. Wrong argument passing to super()

# Wrong - passing arguments incorrectly
class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__()  # Missing arguments!

# Wrong - passing self explicitly (Python 2 style)
class Student(Person):
    def __init__(self, name, age, grade):
        super(Student, self).__init__(name, age)  # Verbose

# Correct (Python 3) - super() figures it out
class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)  # Clean!

4. Overriding without understanding

# Wrong - accidentally breaking parent functionality
class SafeList(list):
    def append(self, item):
        print(f"Adding: {item}")
        # Forgot to actually append! List stays empty.

# Correct - call parent to preserve functionality
class SafeList(list):
    def append(self, item):
        print(f"Adding: {item}")
        super().append(item)  # Actually add the item

5. Confusing MRO in multiple inheritance

# Confusing - which method gets called?
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):  # Which method does D use?
    pass

d = D()
d.method()  # Returns "B" - follows MRO: D -> B -> C -> A

# Check MRO to understand:
print(D.__mro__)  # Shows the lookup order

Exercise: Employee Hierarchy

Task: Create a class hierarchy for employees using inheritance.

Requirements:

  • Manager class: inherits from Employee, adds department and team management
  • Developer class: inherits from Employee, adds programming languages
  • TechLead class: inherits from both Developer and Manager (multiple inheritance)
  • Override describe() in each child class to include additional info
Output
Click Run to execute your code
Show Solution
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def get_annual_salary(self):
        return self.salary * 12

    def describe(self):
        return f"{self.name}, Salary: ${self.salary:,}/month"


class Manager(Employee):
    def __init__(self, name, salary, department):
        super().__init__(name, salary)
        self.department = department
        self.team = []

    def add_report(self, employee):
        self.team.append(employee)

    def describe(self):
        base = super().describe()
        return f"{base}, Dept: {self.department}, Team: {len(self.team)}"


class Developer(Employee):
    def __init__(self, name, salary, languages=None):
        super().__init__(name, salary)
        self.programming_languages = languages or []

    def add_language(self, language):
        self.programming_languages.append(language)

    def describe(self):
        base = super().describe()
        langs = ", ".join(self.programming_languages)
        return f"{base}, Languages: [{langs}]"


class TechLead(Developer, Manager):
    def __init__(self, name, salary, department, languages=None):
        # Need to handle multiple inheritance carefully
        Employee.__init__(self, name, salary)
        self.department = department
        self.team = []
        self.programming_languages = languages or []

    def describe(self):
        langs = ", ".join(self.programming_languages)
        return (f"{self.name}, ${self.salary:,}/month, "
                f"Dept: {self.department}, Team: {len(self.team)}, "
                f"Languages: [{langs}]")


# Test
dev1 = Developer("Alice", 6000, ["Python", "JavaScript"])
mgr = Manager("Carol", 8000, "Engineering")
mgr.add_report(dev1)
lead = TechLead("David", 9000, "Platform", ["Python", "Go"])
lead.add_report(dev1)
lead.add_language("Rust")

print(dev1.describe())
print(mgr.describe())
print(lead.describe())

Summary

  • Inheritance syntax: class Child(Parent): creates a child class
  • super() calls parent class methods, essential for __init__
  • Method overriding lets children replace or extend parent behavior
  • isinstance() checks if an object is an instance of a class or its parent
  • Multiple inheritance: class Child(Parent1, Parent2): - use MRO to understand method lookup
  • Mixins and composition are often better alternatives to complex inheritance

What's Next?

Now that you understand inheritance, learn about Encapsulation - controlling access to attributes and methods using Python's naming conventions for public, protected, and private members.