shoutgreetwrapper1def shout(func):2 def wrapper(name):3 return func(name).upper()4 return wrapper5@shout6def greet(name):7 return "hi " + name8print(greet("sam"))fn shoutfn greetfn greetfn greetfn wrapper'sam''sam''hi sam''HI SAM'@wraps(func)
Code card: deco.py; the highlighted line is the one runningSand blocks: function objects (shout, the original greet, wrapper)Closure cell func: the original greet, kept by wrapperMemory boxes: the global names shout and greetA decorator is a function that takes a function and returns a new one. @shout above def greet means greet = shout(greet). Function objects are the sand blocks. shout receives the original greet and builds wrapper, which keeps the original in its closure cell func; the name greet then moves to wrapper. Calling greet("sam") runs wrapper, which calls the original through func and changes its answer: HI SAM. In the last step @wraps(func) gives wrapper the original's name.

Functions are values

  1. A decorator is a function that takes a function and returns a new one. Line 1 defines ours, shout. Like any def, it builds a function object and stores it under its name. Its body waits.

What @ does

Calling greet

Good habits

Try it yourself

;; step 1/10 · line 1: def shout(func):
>>> def shout(func):
...     def wrapper(name):
...         return func(name).upper()
...     return wrapper
...
>>> shout            # nothing inside has run yet
<function shout at 0x…>

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…>.