19  Classes Inheritance and Composition

TipLearning Objectives
  • Understand what class inheritance and compostion are and how to use them

19.1 Introduction

Object-Oriented Programming allows classes to relate to one another in structured ways.

Two of the most important relationships are inheritance and composition. Understanding when and how to use each is essential for writing maintainable Python code. If you think about the initial examples of where OOP was used, by the end you will understand why inheritance and composition are such benefits.


19.2 Inheritance and Subclasses

Inheritance allows a new class (the subclass) to reuse and extend an existing class (the parent or base class). This promotes code reuse and logical organization.

19.2.1 Basic Inheritance

Let’s say we define a base class with a move function:

class Animal:
    def move(self):
        print("The animal moves")

We can then define a dog class which inherits from it, but also adds the extra speak function

class Dog(Animal):
    def speak(self):
        print("Woof!")
dog1 = Dog()
dog1.move() 
dog1.speak()

Key ideas:

  • Dog inherits from Animal
  • The subclass automatically gains access to the parent’s methods
  • Subclasses can add new behavior

19.3 Instance attributes, __init__, and inheritance

In the previous example there are no instance attributes at all. Neither Animal nor Dog defines an __init__ method. No attributes are assigned using self.something = ….

As a result, dog1 has no instance-specific data. dog1.__dict__ is empty — there is no stored state

Now look at the following example:

class Animal:
    def __init__(self, name):
        self.name = name  # instance attribute

    def move(self):
        print(f"{self.name} moves")

Now every Animal (and every subclass) object will have a name.

Instance attributes and inheritance

When a subclass does not define its own__init__, it automatically inherits the parent’s __init__. So the following works:

class Dog(Animal):
    def speak(self):
        print("Woof!")

dog1 = Dog("Buddy")
dog1.move()    # Buddy moves
dog1.speak()   # Woof!

dog1 has a name attribute created by Animal.__init__ Dog inherits both the attribute setup and the methods.

Adding new instance attributes in the subclass If the subclass needs additional attributes, it defines its own __init__ and calls the parent’s initializer with super().__init__:

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # initialize Animal part
        self.breed = breed      # new instance attribute

    def speak(self):
        print("Woof!")

Now:

  • name comes from Animal
  • breed comes from Dog
  • dog1 contains both attributes

Key Points:

  • Instance attributes exist only when you assign to self
  • Inheritance gives subclasses access to Parent methods through Parent __init__ (if not overridden)
  • super() ensures parent attributes are properly initialized

19.4 Overriding Methods

A subclass can override a method from its parent class to change or specialize behavior.

class Dog(Animal):
    def move(self):
        print("The dog runs swiftly")

Now, calling move() on a Dog object uses the subclass version instead of the parent version.

Overriding is useful when:

  • The subclass needs different behavior
  • The method name and purpose remain the same

19.5 Multiple Levels of Inheritance

Inheritance can form a hierarchy with multiple levels:

class Puppy(Dog):
    def play(self):
        print("The puppy is playing")

Puppy inherits from Dog.

Dog inherits from Animal.

Puppy can therefore use methods from both parent classes. This allows complex behavior to be built gradually from simpler classes.

19.6 Deleting Attributes, Methods, and Objects

Python provides the del keyword to remove attributes, methods, or object references.

Deleting Attributes del dog1.age Removes the attribute from the object Accessing it afterward raises an error Rarely used outside debugging or special cases

Deleting Methods Methods usually belong to the class, not the object.

del Dog.speak

Important notes:

  • Affects all instances of the class

Generally discouraged

Methods should normally be removed by editing the class definition

You can also use del to delete a specific object

19.7 Common Inheritance Mistakes

1. Using Inheritance Without an “Is-a” Relationship

Inheritance should represent a true is-a relationship.

Correct: A Dog is an Animal Incorrect: A Car is an Engine

If the relationship is not “is-a”, inheritance is usually the wrong choice.

2. Creating Deep or Complex Inheritance Trees

Deep inheritance hierarchies are hard to understand and increase the risk of unintended behavior They make debugging difficult. Prefer shallow hierarchies with clear responsibilities.

3. Overriding Methods Incorrectly

Overridden methods should preserve the original meaning, accept compatible inputs and produce expected outputs. Breaking these assumptions can lead to fragile code.

19.8 Composition: An Alternative to Inheritance

Inheritance is not the only way to reuse functionality. Composition is often a better and more flexible choice.

What Is Composition?

Composition means building a class by including other objects as attributes, rather than inheriting from them.

Inheritance models an ‘is-a relationship’ Composition models a ‘has-a relationship’

Examples:

  • A Car has an Engine
  • A GameCharacter has a Weapon
  • A NeuralNetwork has layers

How Composition Works Conceptually

Instead of inheriting behavior, a class:

  • Stores another object internally
  • Delegates work to that object
  • Can replace or modify the contained object as needed: e.g. a game character can change equipment without changing its class

This creates loose coupling between components.

Benefits of Composition; - Increases flexibility - Avoids rigid class hierarchies - Makes behavior easier to change or extend - Encourages reusable, independent components

19.9 Inheritance vs Composition

Use inheritance when:

  • There is a clear is-a relationship
  • The subclass is a specialized version of the parent
  • Shared behavior makes conceptual sense

Use composition when:

  • One object depends on another but is not a subtype
  • Behavior should be interchangeable
  • Flexibility and maintainability are priorities

Composition is often better than inheritance

19.10 Summary

  • Inheritance allows classes to reuse and extend behavior
  • Subclasses can override and specialize methods
  • Improper inheritance can lead to fragile designs
  • Composition provides a flexible alternative using “has-a” relationships
  • Understanding both approaches leads to better object-oriented design
  • Object-Oriented Programming helps structure programs in a way that is scalable, maintainable, and aligned with real-world relationships.