Card 06/ 08
Python's @decorator Syntax vs the Decorator Pattern
Both wrap something, but one wraps a function at definition time and the other an object at run time.
Python's @decorator syntax and the Decorator pattern share a name and an idea, wrapping, and they are not the same thing. The syntax wraps a function, once, when the function is defined. The pattern wraps an object behind an interface, while the program runs. This card asks you to predict the order of two stacked @ lines, then sets the two side by side.
A function decorator is a function that takes a function and returns another. Writing @bold above a definition means greet = bold(greet). Here two are stacked, and each prints a line when it is applied.
@bold
@italic
def greet():
return "hello"Which decorator is applied first, and what does greet() return? Decide, then look.
import functools
def bold(func):
print(f"bold wraps {func.__name__}")
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic(func):
print(f"italic wraps {func.__name__}")
@functools.wraps(func)
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold
@italic
def greet():
return "hello"
print(greet())
print(f"greet.__name__ is {greet.__name__!r}")italic wraps greet
bold wraps greet
<b><i>hello</i></b>
greet.__name__ is 'wrapper'The decorator nearest the function is applied first, so the lines mean greet = bold(italic(greet)). italic wraps first, bold wraps the result, and the call returns <b><i>hello</i></b>, the same inside-out order as the pattern's stack. The last line shows a cost of the syntax: bold returned its inner wrapper without functools.wraps, so the function is now called wrapper. italic used functools.wraps, which copies the name and docstring across, and that is why bold still saw the name greet.
| Question | Python's @ syntax | Decorator pattern |
|---|---|---|
| What is wrapped | A function or a class | An object |
| When | Once, when the definition runs | At run time, to any object, any number of times |
| Shared interface | None required; the wrapper may change the arguments | The wrapper implements the component's interface |
| Per object | No: every call to the function is wrapped | Yes: one object can be wrapped and another left plain |
So @ is related to the pattern without being it. A function decorator can be used to write a Decorator, and both rely on wrapping and inside-out order, but nothing about @ requires a shared interface or lets one caller choose its own stack. When you need to add behaviour to some objects and not others, while the program runs, that is the pattern, and Python writes it with classes that share an interface.
← → move through sections and cards