Decorators
A decorator is a function that takes a function and returns a new one, and @ is the shorthand that applies it. Step through a four-line decorator, shout, as Python hands greet to it, builds a wrapper that remembers the original in a closure, moves the name greet onto the wrapper, and turns 'hi sam' into HI SAM. Finish with functools.wraps, the one line every decorator should have.
5 quick questions
Pick an answer to see why. Your score saves with your progress.
01What does Python do with this code? @log def add(a, b): ...
A decorator is applied once, when the function is defined: Python builds the function, calls log with it, and binds the name add to whatever log returns.
02After @shout is applied, what does the name greet point to?
The name is bound to the decorator's return value, here wrapper. The original greet survives only inside wrapper's closure, as func.
03Inside wrapper, how can it still call the original greet after shout has returned?
wrapper uses func, a name from the enclosing function shout, so Python keeps it in a closure cell (greet.__closure__). Looking up the global greet would find wrapper itself.
04What does this print? def twice(f): def wrapper(x): return f(f(x)) return wrapper @twice def inc(x): return x + 1 print(inc(5))
inc now names wrapper, which calls the original inc twice: 5 + 1 is 6, and 6 + 1 is 7.
05Why put @functools.wraps(func) on the wrapper inside a decorator?
Without wraps, greet.__name__ is 'wrapper' and help(greet) shows the wrapper's empty docstring. wraps copies the metadata, so error messages and tools show the real name.
Glossary
- Decorator
- A function that takes a function and returns a new one, usually applied with @name on the line above a def.
- First-class function
- A function treated like any other value: stored in a name, passed as an argument, returned from another function.
- Wrapper
- The new function a decorator returns. It usually calls the original and adds something before or after.
- Closure
- A function that keeps names from the function it was defined in, such as func inside wrapper, even after that function has returned.
- @ syntax
- @shout above def greet means greet = shout(greet), done once when the def runs.
- functools.wraps
- A decorator for wrappers: it copies the original function's name, docstring and other details onto the wrapper and sets __wrapped__.
- *args, **kwargs
- Parameters that collect any positional and keyword arguments, so a wrapper can pass them all on to the original function.
Notes and sources
- The order is exact: Python evaluates the decorator expression (shout), builds the function from def, calls the decorator with it, and only then binds the name. The reference says @shout def greet is roughly greet = shout(greet), except that the original is never bound to the name greet, even for a moment.
- The figure writes a function object as fn and its __name__. Python shows it as <function greet at 0x…>; the number is a memory address that changes from run to run. Without wraps, Python shows the wrapper as <function shout.<locals>.wrapper at 0x…>.
- The closure cell is real: greet.__closure__[0].cell_contents is the original function, and greet.__code__.co_freevars is ('func',). Each call of shout makes a fresh wrapper with its own cell.
- functools.wraps(func) copies __module__, __name__, __qualname__, __doc__ and a few more attributes from func onto the wrapper, updates its __dict__, and sets __wrapped__ to the original function.
- Real decorators usually define wrapper(*args, **kwargs) and call func(*args, **kwargs), so they work for a function with any parameters. Several decorators can be stacked; the one nearest the def is applied first.
- Python reference: Function definitions (decorators)
- Python glossary: decorator
- Python reference: Execution model, resolution of names (closures)
- Python reference: Data model, user-defined functions (__name__, __closure__)
- Python library: functools.wraps
- Python library: str.upper
- PEP 318: Decorators for Functions and Methods
Quiz Unlocks after the last step.