Decorators
You'll learn to
- -Explain what a decorator is: a function that wraps another function
- -Apply a decorator with `@decorator_name` syntax
- -Build a simple timing or logging decorator from scratch
A decorator is a function that takes another function as input and returns a new function that wraps it - typically adding behavior before and/or after the original runs, without modifying the original function's own code. This directly builds on the closures chapter from the Functions module: a decorator is a closure whose "remembered" enclosing variable happens to be the function it is wrapping.
Building a Decorator by Hand
Before the `@` syntax, look at what a decorator actually is: a function that accepts a function and returns a new function.
Notice `wrapper` takes `*args, **kwargs` - exactly the "accept anything" tool from the earlier functions module - so `log_calls` can wrap a function with any signature at all, not just ones that take two arguments.
The @decorator Syntax
`@decorator_name` placed directly above a function definition is exactly equivalent to the manual reassignment above - `def add(...): ...` followed by `add = log_calls(add)` - just cleaner to read.
A Timing Decorator
Another classic, genuinely useful decorator: measuring how long a function takes to run, using the `time` module.
Foreshadowing: Caching Is Just a Decorator
Here is the payoff for everything in this chapter and the closures chapter before it. What if, instead of timing a function, a wrapper checked whether it had already computed the result for these exact arguments, and returned the cached answer instead of recomputing it?
This is exactly what memoization is: a cache, keyed by arguments, wrapped around a function that would otherwise repeat expensive work. The Algorithms phase of this course covers memoization for recursive algorithms in depth (and Python's own `functools.lru_cache` gives you this decorator for free) - but the underlying mechanism is precisely the closure-plus-wrapper pattern you just built by hand.
- -A decorator is a function that takes a function and returns a new (usually wrapping) function.
- -`@decorator_name` above a `def` is sugar for `func = decorator_name(func)`.
- -A wrapper almost always accepts `*args, **kwargs` so it can wrap functions of any signature.
- -A caching decorator is a closure holding a dict, checked and updated on every call - this is memoization, which the next phase of this course (Algorithms) builds on directly.
What does `@log_calls` placed above `def add(a, b):` actually do?