Python - Decorators

Overview

Estimated time: 30–40 minutes

Decorators wrap callables to add behavior (logging, caching, validation). Learn how they work and how to build reliable decorators.

Learning Objectives

  • Explain how decorators are just callables taking and returning callables.
  • Write decorators that preserve metadata with functools.wraps.
  • Use common patterns: memoization (lru_cache), retry wrappers, access checks.

Prerequisites

  • Functions and closures

Examples

from functools import wraps

def shout(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        result = fn(*args, **kwargs)
        return str(result).upper()
    return wrapper

@shout
def greet(name):
    return f"Hello, {name}!"

print(greet("Ada"))

Expected Output:

HELLO, ADA!

Guidance & Patterns

  • Explain that @decorator is syntax sugar for fn = decorator(fn).
  • Demonstrate parameterized decorators (@decorator(arg)) that return a decorator.
  • Show how missing @wraps breaks __name__/__doc__ and tooling.

Best Practices

  • Prefer composition over complex, stateful decorators. Keep wrappers thin and testable.
  • For caching, prefer functools.lru_cache over homegrown caches; document invalidation strategy.
  • Mind async functions: use async def wrappers and consider anyio for structured concurrency.

Exercises

  1. Implement a @retry(n=3) decorator with exponential backoff for exceptions.
  2. Write a decorator that validates function arguments using type hints at runtime.