P29 · Functions, Decorators & Generators
closures, decorators, iterators & generators
Python's functional core, rebuilt by hand: closures that carry state, decorators as ordinary wrap-and-return code, and generators whose suspended frames power lazy pipelines, context managers - and, one pillar on, async/await.
Three features that look like separate pieces of Python syntax — closures, decorators, generators — are one idea seen from three angles: a function is a value, and a function that has been suspended or wrapped is still just a value you can pass around. Getting that straight is unusually high-leverage, because almost everything that feels magical later in this discipline turns out to be built from these three, and this pillar is where they stop being magical.
Closures come first: a function that captures state from its enclosing scope,
rebinds it with nonlocal, and can therefore act as a factory, a partial
application, or a composition of other functions. Decorators are then revealed
as ordinary code — wrap and return, with functools.wraps to keep the identity
of what you wrapped — extended to the three-layer factory that takes arguments,
stacked deliberately so the order is a decision rather than an accident, and
written as classes with __call__ when they need state. Generators come last
and reach furthest: you rebuild the iterator protocol, replace it with yield,
exploit laziness on streams too large or endless to materialise, and then find
that the same suspended frame is what a context manager is made of via
contextlib, and what send() turns into a coroutine — which is exactly the
door P33 walks through to reach async/await.