Step 1 of 7 · Reading · ~3 min
Learn
Decorators
Function Decorators
You will write decorators that wrap, register, count and cache functions, and predict their order. A decorator is the closure from Higher-Order Functions whose captured variable is a function.
What @ does, and when
@d above a def means f = d(f), run once when the def executes, not per call. Returning the function unchanged makes a registration hook:
@on("save") calls on("save") first, then applies the result. Without parentheses, @on calls on(backup) and backup becomes the inner decorator.
The wrapper
To change behaviour, return a new function that accepts any call and returns what the original returned:
Two bugs: no return result, so the caller gets None, and area is now wrapper. @functools.wraps(func) on the wrapper copies __name__, __doc__ and __dict__ from func and sets __wrapped__ to the original.
Stacking and arguments
They apply bottom-up and run top-down:
Swap the two lines and the output is <i><b>hi</b></i>.
State, and the wraps trap
A counter can live on the wrapper, until another decorator is stacked above it:
wraps copied calls = 0 onto the outer wrapper when @loud ran, and that int never updates. Keep counts where every layer shares them, such as a module-level dict. (nonlocal suits state private to the wrapper.)
functools.lru_cache caches by argument; maxsize=None is unbounded and cache_info() reports what happened:
A hand-written cache is a dict keyed by the arguments; where it lives matters. At module level, two cached functions share it: double(5) then triple(5) both give 10. Created inside the wrapper, it is empty on every call.
@contextmanager
It turns a generator into a with block: code before yield is __enter__, code after is __exit__.
Without try/finally, an exception in the block skips the closing line. On a method, self is just args[0]; put @property and @classmethod above your decorator.
Common mistakes
- No
returnin the wrapper: the caller getsNone. - Missing
@wraps:func.__name__through a layer is'wrapper', so a lookup by name finds nothing. - Counter and cache in the wrong order:
@countedabove@memoizecounts hits too; below it, only misses. - One cache for several functions: a call returns another function's answer.
Your exercise
Build a command registry: @command registers, @counted counts, @memoize caches, and fib stacks all three, so stats shows how often its body really ran. The dispatcher is given; the decorators are yours. The most likely mistake is a counter that survives only some layers.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…