Advanced Python — Decorators, Generators & Type Hints
Closures: The Foundation of Decorators
Before decorators make sense, closures must make sense.
A closure is a function that captures variables from its enclosing scope — even after the enclosing function has returned. Python implements this with __closure__, a tuple of cell objects that hold the captured values.
Decorators: Syntactic Sugar for Higher-Order Functions
A decorator is a callable that takes a function and returns a new function. The @decorator syntax is literally just:
The key tool is functools.wraps — without it, the wrapper replaces the original function's __name__, __doc__, and other metadata, which breaks introspection and documentation.
Decorator Factories (Decorators with Arguments)
When you need to configure a decorator, you add another layer: a factory function that takes the configuration and returns the actual decorator.
Class-Based Decorators and Built-in Decorators
Any callable can be a decorator, including classes. A class-based decorator is useful when you need to maintain state across calls.
Generators: Lazy Evaluation at Its Finest
A generator is a function that yields values one at a time, pausing its execution between yields. It doesn't compute everything upfront — it computes the next value when asked. This makes generators:
- Memory-efficient: a generator over 1 million items uses O(1) memory
- Composable: generators can be chained into pipelines
- Interruptible: execution can be paused, resumed, and even injected into with
send()
itertools: The Generator Toolkit
Type Hints: Making Python's Dynamic System Explicit
Type hints don't change runtime behavior — Python remains dynamically typed. But they power static analysis tools (mypy, pyright), improve IDE autocomplete, and serve as executable documentation.
Python 3.10+ Pattern Matching
The match statement is structural pattern matching — it's not a C-style switch. It can destructure objects, sequences, and mappings:
PROJECT: Retry Decorator System
PROJECT: Memory-Efficient Data Pipeline
Key Takeaways
- Closures capture by reference, not by value: the loop variable pitfall (
lambda: iin a loop) bites everyone once; fix with a default argumentlambda i=i: i functools.wrapsis not optional: without it, decorators silently destroy__name__,__doc__, and__wrapped__, breaking reflection, logging, and documentation tools- Decorator factories add one more level of indirection:
@retry(max_attempts=3)is a three-level structure — factory → decorator → wrapper — which is why the return structure looks nested - Generators are pull-based: computation only happens when the caller requests the next value via
next()or iteration; a generator pipeline over 1M rows uses O(1) memory send()makes generators two-way channels: you can push values into a paused generator, making them useful for coroutines and state machinesitertoolsis the standard library for lazy sequences:chain,islice,groupby,accumulate,product— learn them and you'll stop writing manual loops- Type hints are documentation that runs: they don't enforce types at runtime, but mypy/pyright catches mismatches statically;
Protocolenables duck-typed interfaces without inheritance - Pattern matching (
match) is structural: it's not aswitchstatement — it can destructure dicts, sequences, and instances, with guard clauses (if condition) for complex filtering