Content · Programming · Python

Python Favor Composition Over Deep Inheritance

Prefer putting a cooperating object inside a class when inheritance adds no natural is-a relationship.

2 views Published Oct 17, 2026 Updated Oct 18, 2026 Admin

Prefer putting a cooperating object inside a class when inheritance adds no natural is-a relationship.

What You Will Learn


Composition means one object delegates part of its work to another. It helps when you may change how a task is done without changing the main class hierarchy. Inheritance should describe a meaningful specialized kind of object; deep inheritance trees make state and method resolution harder to follow.

Class interfaces should preserve their stated invariants. Type annotations describe intent but do not enforce runtime types on their own.

Step-by-Step Example


The example below makes the behavior visible with small input values that you can change safely.

class EmailSender:
    def send(self, message):
        return f"Email: {message}"

class Notifier:
    def __init__(self, sender):
        self.sender = sender

    def notify(self, message):
        return self.sender.send(message)

notification = Notifier(EmailSender())
print(notification.notify("Report ready"))

How the Example Works


  • The definitions EmailSender, Notifier separate the main steps into named components; calls to those components drive the example.
  • The variable notification holds the result of Notifier(EmailSender()). Later lines use that value or inspect how it changes.
  • The print() calls make the return values or state visible so you can compare them with the explanation and check your prediction.

For the chosen input, the expression notification.notify('Report ready') prints Email: Report ready. Compare that result with the rule above before you change the argument.

Expected output from the example:

Email: Report ready

When You Would Use This


A class becomes useful when several operations must maintain rules around related data. Think about which state belongs to each instance, which operations should be public, and how errors should be reported. Avoid making the design more complex than the underlying task.

Practice: Modify the Code


Add a ConsoleSender with the same send interface without changing Notifier.

Verify the changed output against the behavior described above, including the relevant invalid or boundary case.

For complete parameters and version details, consult the official Python documentation.

Common Beginner Mistakes


  • Making Notifier inherit from EmailSender even though a notifier is not necessarily an email sender.
  • Hardcoding a dependency inside every method instead of injecting it once.

Key Takeaways


  • Prefer putting a cooperating object inside a class when inheritance adds no natural is-a relationship.
  • Composition means one object delegates part of its work to another.
  • Apply this operation only after checking the assumptions and input conditions shown in the example.
Share this resourceSend this page to someone who may find it useful.