Featured image of post Python Inheritance vs Composition

Python Inheritance vs Composition

Python Inheritance vs Composition

Inheritance vs. Composition in Python: What’s the Difference?

🎧 Prefer audio?

Listen to the podcast version:

When designing clean and maintainable code using object-oriented programming (OOP) in Python, two fundamental concepts you’ll encounter are inheritance and composition.
Both are powerful models for code reuse and extensibility, but they follow different philosophies and offer unique advantages depending on the situation.

What is Inheritance?

Inheritance allows one class (child) to reuse code from another (parent). This forms an “is-a” relationship: a Dog is an Animal.

class Animal:
    """
    Animal class that can produce a generic sound.

    Methods:
        make_sound(sound: str) -> str:
            Returns a string representing the sound the animal makes.
    """
    def make_sound(self, sound: str) -> str:
        return f"The animal says: {sound}"


class Dog(Animal):
    """
    Dog class that inherits from Animal and represents a specific animal behavior.

    Methods:
        bark() -> str:
            Returns the sound specific to dogs by calling make_sound with "Woof".
    """
    def bark(self) -> str:
        return self.make_sound("Woof")


# Usage
dog = Dog()
print(dog.bark())  # Output: The animal says: Woof

Inheritance:

  • Great for creating hierarchies where subclasses share behavior from a superclass.

Drawbacks:

  • Strong coupling between base and derived classes.
  • Inflexible when behaviors vary significantly.

What is Composition?

Composition models a “has-a” relationship by including one object inside another. It’s more flexible and promotes loose coupling.

class Engine:
    """
    Engine class that represents a generic engine capable of starting.

    Methods:
        start() -> str:
            Simulates starting the engine and returns a status message.
    """
    def start(self) -> str:
        return "Engine starts"


class Car:
    """
    Car class that uses an Engine instance via composition.

    Constructor:
        __init__(engine: Engine):
            Initializes the car with a specific engine.

    Methods:
        start() -> str:
            Starts the car by delegating to the Engine's start method.
    """
    def __init__(self, engine: Engine) -> None:
        self.engine = engine

    def start(self) -> str:
        return self.engine.start()


# Usage
engine = Engine()
car = Car(engine)
print(car.start())  # Output: Engine starts

Composition:

  • Enables building complex behavior by combining simple classes.
  • Promotes modularity and reuse without tight inheritance chains.

Features Comparison

FeatureInheritanceComposition
Relationship“is-a”“has-a”
CouplingTightLoose
ReusabilityVertical (from parent)Horizontal (via components)
FlexibilityLess (deep hierarchies)More (behavior swapping is easier)
Best Use CaseNatural hierarchies, shared logicComplex systems, changeable behaviors

Best Practice

Prefer composition over inheritance when:

  • The subclass behavior significantly differs from base class.
  • You want to swap parts at runtime.
  • You’re building reusable and testable components.

Use inheritance when:

  • There’s a clear hierarchical relationship.
  • Shared functionality is unlikely to change.
Built with Hugo + customized Stack theme