Skip to content

Week 6 - Decorators, functools, and contextlib

6.1 Conceptual Core

  • A decorator is just f = decorator(f). The @ is sugar.
  • A useful decorator preserves: name, docstring, signature, type annotations, async-ness, and __wrapped__ for introspection. functools.wraps handles the first three; preserving signature and type requires ParamSpec (PEP 612).
  • Class decorators decorate the class object itself. @dataclass is the canonical example.

6.2 Mechanical Detail

  • functools.wraps, functools.partial, functools.partialmethod, functools.lru_cache (and cache in 3.9+ for unbounded), functools.singledispatch, functools.singledispatchmethod, functools.reduce (rarely the right tool - usually a comprehension or sum).
  • Type-preserving decorators with ParamSpec and TypeVar:
    from typing import Callable, ParamSpec, TypeVar
    P = ParamSpec("P"); R = TypeVar("R")
    def timed(fn: Callable[P, R]) -> Callable[P, R]: ...
    
  • contextlib.contextmanager for generator-based context managers; contextlib.asynccontextmanager for async.
  • contextlib.ExitStack / AsyncExitStack: the right tool for a dynamic number of context managers (e.g., opening a list of files determined at runtime).
  • contextlib.suppress, contextlib.closing, contextlib.redirect_stdout.

6.3 Lab - "The Retry Decorator That Doesn't Lie About Its Type"

  1. Write @retry(times=3, on=(IOError,), backoff=0.1). Make it work on both sync and async functions (detect with asyncio.iscoroutinefunction).
  2. Use ParamSpec so that pyright --strict preserves the wrapped signature.
  3. Add structured logging on each retry. Add a tenacity-style backoff strategy (constant, exponential, jittered).
  4. Compare to tenacity library; document where yours is simpler / worse / better.

6.4 Idiomatic & Linter Drill

  • Enable ruff FBT (boolean-trap), ARG (unused arguments). Refactor decorators to take keyword-only configuration.

6.5 Production Hardening Slice

  • Add mypy (in addition to pyright) with strict_optional, disallow_any_generics. The two type checkers disagree on edge cases; configuring both surfaces those.

Comments