Lab 02: Decorators

Objective

Write production-quality decorators: functools.wraps, parameterized decorators, class decorators, LRU caching, retry with backoff, rate limiting, and timing.

Time

30 minutes

Prerequisites

  • Lab 01 (Advanced OOP)

Tools

  • Docker image: zchencow/innozverse-python:latest


Lab Instructions

Step 1: Decorator Fundamentals & functools.wraps

docker run --rm zchencow/innozverse-python:latest python3 -c "
import functools
import time

# Without wraps — loses metadata
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# With wraps — preserves __name__, __doc__, __module__
def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def bad_fn():
    '''Bad function docstring'''
    pass

@good_decorator
def good_fn():
    '''Good function docstring'''
    pass

print('bad_fn name:', bad_fn.__name__)   # wrapper
print('good_fn name:', good_fn.__name__) # good_fn
print('good_fn doc:', good_fn.__doc__)

# Timing decorator
def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f'[timer] {func.__name__} took {elapsed*1000:.2f}ms')
        return result
    return wrapper

@timer
def slow_sum(n: int) -> int:
    return sum(range(n))

result = slow_sum(1_000_000)
print(f'slow_sum result: {result:,}')
"

💡 Always use @functools.wraps(func) inside decorators — it copies __name__, __doc__, __annotations__, and __wrapped__ from the original function. Without it, debugging, documentation, and help() all show wrapper instead of the real function name.

📸 Verified Output:


Step 2: Parameterized Decorators

📸 Verified Output:


Steps 3–8: LRU Cache, Class Decorators, Memoize, Logging, Context Manager Decorator, Capstone

📸 Verified Output:


Summary

Pattern
Syntax
Use case

Basic decorator

def dec(func): @wraps(func) def wrapper...

Any function wrapping

Parameterized

def dec(arg): def decorator(func):...

Configurable behavior

Class decorator

class Dec: def __call__(self,...):

Stateful decorators

@lru_cache

@functools.lru_cache(maxsize=N)

Memoize pure functions

TTL cache

Custom with time.monotonic()

Expire-able cache

Stacking

@dec1 @dec2 @dec3

Applied bottom-up

Further Reading

Last updated