Decorators & Context Managers
Wrapping a function's behavior without changing its code, and guaranteeing setup/teardown runs even when something fails.
Decorators
A decorator is a function that takes a function and returns a (usually wrapped) function — @my_decorator above a function definition is shorthand for my_func = my_decorator(my_func). This is how logging, timing, caching (functools.lru_cache), and access control get added to a function without touching its body.
Context managers
The with statement guarantees a cleanup step runs even if the block raises an exception — with open(f) as file: closes the file whether the code inside succeeds, fails, or returns early. A class implements this via __enter__/__exit__; @contextmanager from contextlib lets you write the same thing as a single generator function with one yield splitting setup from teardown.
Why they're related
Both are about wrapping behavior around a piece of code — a decorator wraps a function call, a context manager wraps a block of statements — and both rely on closures capturing state between the "before" and "after" parts.
Prerequisite
Understanding classes and functions as first-class objects (from OOP) is assumed, since decorators are just functions operating on other functions.
