← Back to list

Decorator Design Pattern in Python: A Complete Practical Guide for Modern Backend Engineers

Learn the Decorator Design Pattern in Python with real-world examples, FastAPI use cases, production best practices

Manohar · 2026-06-03 02:39 · 0 claps · 3.3 min read
#decorator-design-pattern #structural-design-pattern #software-engineering #python-production
Open on Medium ↗
Wiki topics: 🌐 · Web Development

Decorator Design Pattern in Python: A Complete Practical Guide for Modern Backend Engineers

Learn the Decorator Design Pattern in Python with real-world examples, FastAPI use cases, production best practices

Introduction

One of the most common problems in software engineering is this:

“How do we add behavior to an object without modifying its existing code?”

As applications grow, we often need to add:

  • logging
  • caching
  • authentication
  • monitoring
  • retries
  • compression
  • rate limiting
  • metrics

If we directly modify existing classes every time we need new behavior, the codebase quickly becomes:

  • tightly coupled
  • hard to maintain
  • difficult to test
  • full of duplicated logic

This is exactly where the Decorator Design Pattern becomes extremely valuable.

The Decorator Pattern allows you to dynamically add responsibilities to objects without changing their original implementation.

It is one of the most heavily used design patterns in modern Python frameworks, including:

  • FastAPI
  • Flask
  • Django
  • Requests
  • Logging frameworks
  • Async middleware systems

In fact, Python’s @decorator syntax is heavily inspired by this design pattern.

What Is the Decorator Design Pattern?

The Decorator Pattern is a structural design pattern that allows behavior to be added to objects dynamically by wrapping them inside another object.

Instead of modifying the original object:

  • we wrap it
  • intercept calls
  • add extra functionality
  • forward execution to the wrapped object

Think of it as layering additional capabilities around an object.

The Problem It Solves

Imagine a notification system.

Initially:

class EmailNotifier:
    def send(self, message: str) -> None:
        print(f"Sending email: {message}")

Later requirements arrive:

  • Send SMS too
  • Add Slack notifications
  • Add logging
  • Add encryption
  • Add retry handling

A beginner approach usually becomes:

class EmailSMSNotifier:
    ...

class EmailSMSSlackNotifier:
    ...

class EmailSMSSlackLoggingNotifier:
    ...

This quickly becomes impossible to maintain.

The Decorator Pattern solves this by allowing behaviors to be composed dynamically.

Why This Pattern Exists

The Decorator Pattern exists to support:

  • Open/Closed Principle
  • Composition over inheritance
  • Runtime behavior extension
  • Flexible object enhancement

Instead of creating massive inheritance trees, we compose behaviors dynamically.

Real-World Analogy

Think about ordering coffee.

You start with:

  • Basic Coffee

Then add decorators:

  • Milk
  • Sugar
  • Caramel
  • Whipped Cream

Each topping wraps the original coffee and adds functionality.

WhippedCream(
    Caramel(
        Milk(
            Coffee()
        )
    )
)

The base object remains unchanged.

Pattern Structure

The Decorator Pattern typically contains:

  1. Component — Defines the common interface.

  2. Concrete Component — The original object.

  3. Base Decorator — Wraps the component and forwards requests.

  4. Concrete Decorators — Add extra behaviors.

Decorator Pattern Architecture

        Component Interface
               ↑
       ┌───────┴────────┐
       │                │
ConcreteComponent   BaseDecorator
                            ↑
                  ┌─────────┴─────────┐
                  │                   │
         LoggingDecorator    CacheDecorator

Step-by-Step Python Implementation

Step 1 — Create Component Interface

from abc import ABC, abstractmethod

class DataSource(ABC):

    @abstractmethod
    def write(self, data: str) -> None:
        pass

    @abstractmethod
    def read(self) -> str:
        pass

Step 2 — Create Concrete Component

class FileDataSource(DataSource):

    def __init__(self, filename: str) -> None:
        self.filename = filename

    def write(self, data: str) -> None:
        with open(self.filename, "w") as file:
            file.write(data)

    def read(self) -> str:
        with open(self.filename, "r") as file:
            return file.read()

Step 3 — Create Base Decorator

class DataSourceDecorator(DataSource):

    def __init__(self, wrapped: DataSource) -> None:
        self._wrapped = wrapped

    def write(self, data: str) -> None:
        self._wrapped.write(data)

    def read(self) -> str:
        return self._wrapped.read()

Step 4 — Create Concrete Decorators

Encryption Decorator

import base64

class EncryptionDecorator(DataSourceDecorator):

    def write(self, data: str) -> None:
        encrypted = base64.b64encode(data.encode()).decode()
        self._wrapped.write(encrypted)

    def read(self) -> str:
        encrypted = self._wrapped.read()
        return base64.b64decode(encrypted.encode()).decode()

Compression Decorator

import zlib

class CompressionDecorator(DataSourceDecorator):

    def write(self, data: str) -> None:
        compressed = zlib.compress(data.encode())
        encoded = compressed.hex()
        self._wrapped.write(encoded)

    def read(self) -> str:
        encoded = self._wrapped.read()
        compressed = bytes.fromhex(encoded)
        return zlib.decompress(compressed).decode()

Step 5 — Use the Decorators

source = FileDataSource("data.txt")

encrypted = EncryptionDecorator(source)

compressed_and_encrypted = CompressionDecorator(
    EncryptionDecorator(source)
)

compressed_and_encrypted.write("Sensitive production data")

result = compressed_and_encrypted.read()

print(result)

Advanced Production Example

API Client with Retry, Logging, and Caching

This is a real production scenario commonly used in backend systems.

Base API Client

from abc import ABC, abstractmethod

class APIClient(ABC):
    @abstractmethod
    def request(self, endpoint: str) -> dict:
        pass

Concrete Implementation

import time

class HttpClient(APIClient):
    def request(self, endpoint: str) -> dict:
        time.sleep(1)
        return {
            "endpoint": endpoint,
            "data": "response"
        }

Base Decorator

class APIClientDecorator(APIClient):
    def __init__(self, client: APIClient) -> None:
        self._client = client
    def request(self, endpoint: str) -> dict:
        return self._client.request(endpoint)

Logging Decorator

import logging

logger = logging.getLogger(__name__)

class LoggingDecorator(APIClientDecorator):
    def request(self, endpoint: str) -> dict:
        logger.info("Calling endpoint: %s", endpoint)
        response = self._client.request(endpoint)
        logger.info("Response received")
        return response

Retry Decorator

import time

class RetryDecorator(APIClientDecorator):
    def __init__(
        self,
        client: APIClient,
        retries: int = 3
    ) -> None:
        super().__init__(client)
        self.retries = retries
    def request(self, endpoint: str) -> dict:
        for attempt in range(self.retries):
            try:
                return self._client.request(endpoint)
            except Exception:
                time.sleep(1)
        raise RuntimeError("Request failed")

Cache Decorator


class CacheDecorator(APIClientDecorator):   
    def __init__(self, client: APIClient) -> None:
        super().__init__(client)
        self.cache: dict[str, dict] = {}
    def request(self, endpoint: str) -> dict:
        if endpoint in self.cache:
            return self.cache[endpoint]
        response = self._client.request(endpoint)
        self.cache[endpoint] = response
        return response

Composition

client = LoggingDecorator(
    RetryDecorator(
        CacheDecorator(
            HttpClient()
        )
    )
)
response = client.request("/users")
print(response)

Conclusion

The Decorator Design Pattern is one of the most practical and widely used patterns in modern Python engineering.

It enables:

  • clean extensibility
  • modular architectures
  • reusable behaviors
  • scalable backend systems

In real-world systems, decorators power:

  • authentication
  • caching
  • retries
  • logging
  • observability
  • middleware
  • rate limiting

Mastering this pattern will significantly improve your ability to design flexible, maintainable, production-grade Python applications.

For backend engineers and FastAPI developers, understanding decorators is not optional anymore — it is a foundational architectural skill.


메타데이터
post_id
d3045d2ceba4
slug
decorator-design-pattern-in-python-a-complete-practical-guide-for-modern-backend-engineers-d3045d2ceba4
url
https://medium.com/@manohar_001/decorator-design-pattern-in-python-a-complete-practical-guide-for-modern-backend-engineers-d3045d2ceba4
canonical_url
https://medium.com/@manohar_001/decorator-design-pattern-in-python-a-complete-practical-guide-for-modern-backend-engineers-d3045d2ceba4
author_url
https://medium.com/@manohar_001
status
ok
fetched_at
2026-06-24 04:09:36