advanced

Decorators & Closures

7 min readLast updated: 2026-07-12

Overview

Decorators let you modify or extend a function's behavior dynamically without modifying its source code.

Learning Objectives

  • Create basic decorators.
  • Preserve original function metadata using wraps.
  • Construct closures that capture outer scopes.

Concept Explanation

A decorator takes a function as an argument, wraps it with custom logic, and returns the wrapper function. Closures allow inner functions to reference variables in enclosing scopes even after the outer function has finished executing.

Code Examples

Example 1 — Basics

This example introduces the fundamental syntax and concepts.

python
def announce(func):
    def wrapper():
        print('Function starting')
        return func()
    return wrapper

Example 2 — Everyday Usage

This example demonstrates a realistic scenario handling business parameters.

python
from functools import wraps

def log_call(func):
    @wraps(func) # Preserves name and metadata
    def wrapper(*args, **kwargs):
        print(f'Running: {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

@log_call
def add(a, b): return a + b
print(add(5, 7))

Example 3 — Advanced Example

This example shows clean, production-grade code structure following senior development standards.

python
# Timer decorator measuring function execution time
import time
from functools import wraps

def measure_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        duration = time.perf_counter() - start
        print(f'{func.__name__} took {duration:.4f} seconds')
        return result
    return wrapper

@measure_time
def compute_squares():
    return [x * x for x in range(10000)]

compute_squares()

Visual Flow

The following execution flow represents the step-by-step evaluation inside the interpreter:

text
Call decorated function → Execute wrapper pre-logic → Run original function → Execute wrapper post-logic → Return output

Common Mistakes

Review these common pitfalls when working with this topic:

  • Forgetting to return the inner wrapper function from the decorator.
  • Not using @wraps, which overwrites the decorated function's name and metadata.
  • Forgetting to pass *args and **kwargs inside the wrapper function.
  • Modifying closure variables directly without nonlocal declarations.

Best Practices

Enforce these Pythonic best practices in your codebase:

❌ Don't
python
def log(f):
    def wrap(*args):
        return f(*args)
    return wrap # Missing wraps metadata preservation
✅ Do
python
from functools import wraps
def log(f):
    @wraps(f)
    def wrap(*args, **kw):
        return f(*args, **kw)
    return wrap # Metadata preserved

Quick Revision

Use these key summaries for last-minute revision:

  • Decorators wrap functions to add behaviors.
  • Closures capture outer variables.
  • Use @functools.wraps to keep metadata.
  • wrapper must accept *args and **kwargs.
  • Apply decorators using the @ symbol.
  • Decorators compile to func = decorator(func).