Python Decorator Patterns
Decorator patterns are reusable wrappers for common jobs.
Time a call
The variable elapsed holds the seconds between the two clock reads. The code does not print elapsed.
import time
def timed(func):
def wrapper():
start = time.perf_counter()
result = func()
elapsed = time.perf_counter() - start
print("Finished:", func.__name__)
return result
return wrapperLog each call
A log is a written record of what happened. This wrapper prints one line before the call and one after it.
from functools import wraps
def logged(func):
@wraps(func)
def wrapper(name):
print("Calling:", func.__name__)
result = func(name)
print("Done:", func.__name__)
return result
return wrapper
@logged
def greet(name):
return "Hello, " + name
print(greet("Ada"))Retry after a failure
Some jobs fail once and then work. The counter tries counts each failure, and the loop stops after three.
def retry(func):
def wrapper():
tries = 0
while tries < 3:
try:
return func()
except ValueError:
tries += 1
print("Try again")
raise ValueError("Still failing")
return wrapperStack two decorators
Python applies a stack from the bottom up. The decorator nearest def runs first, then the one above it.
def add_a(func):
def wrapper():
return "A" + func()
return wrapper
def add_b(func):
def wrapper():
return "B" + func()
return wrapper
@add_a
@add_b
def base():
return "x"
print(base())Example
from functools import wraps
def retry(func):
@wraps(func)
def wrapper():
tries = 0
while tries < 3:
try:
return func()
except ValueError:
tries += 1
print("Failed, tries so far:", tries)
raise ValueError("Still failing after 3 tries")
return wrapper
calls = 0
@retry
def load_report():
global calls
calls += 1
if calls < 3:
raise ValueError("Not ready yet")
return "Weather report"
print(load_report())
print("Calls made:", calls)Key points
- Timing decorator: a wrapper that measures how long a call takes
- Use time.perf_counter() to read a precise clock before and after a call
- Logging decorator: a wrapper that prints before and after each call
- Retry decorator: a wrapper that calls the function again after a failure
- Counter: a variable that counts tries, so the wrapper can stop
- Stacking: with two decorators, the one nearest the function applies first
In this example, retry wraps load_report. The variable calls counts every real try. The first call raises ValueError. The wrapper prints the try count. It then calls load_report again. The second call also raises ValueError. The wrapper prints again and tries once more. The third call returns the report text. The wrapper hands that text back unchanged. The last line proves the function ran three times.
Note
A measured time changes on every run.
Only the shape of a timing result is fixed, never its seconds.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Python Playground
Exercise
Two decorators sit above a function. Which one is applied first?