Wrapping functions & functools.wraps
Decorators
Lesson 4 of 20 · 12 min · Module 1: Functions, deeper
Logging, timing, caching, and access checks often apply to many functions. A decorator adds such behaviour without changing the function’s own code.
Start this lesson →Free. No installation: you write and run the code in your browser, and it is checked automatically.
By the end, you’ll be able to…
- Explain what a decorator does
- Write a wrapper that forwards any arguments
- Preserve function metadata with functools.wraps
The idea
A decorator is a function that takes a function and returns a new one, usually a wrapper that calls the original. @count_calls above a def is shorthand for f = count_calls(f). Use functools.wraps so the wrapper keeps the original name and docstring.
import functools
def shout(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
@shout
def greet(name):
return "hi " + name
print(greet("sam"))
01The @ syntax is shorthand
@count_calls on top of def square(x) runs square = count_calls(square). After that, the name square refers to the wrapper.
02Forward everything
wrapper(*args, **kwargs) accepts any call, and func(*args, **kwargs) passes it on unchanged. That makes one decorator usable for every function.
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
return wrapper
@log
def add(a, b):
return a + b
print(add(2, 3))calling add
503Functions can have attributes
A function is an object, so wrapper.calls = 0 stores data on it. functools.wraps copies the original __name__ and __doc__ to the wrapper, which keeps error messages and documentation tools accurate.
Your turn
count_calls. The wrapped function must return exactly what the original returns, accept any arguments, keep its __name__, and expose a calls attribute that counts how many times it has been called.What the checks look for
- Counts calls
- Returns the original result and passes keyword arguments
- Starts at zero and counts separately per function
- Keeps the original function name
Take this with you
A decorator wraps behaviour around a function; wraps keeps its identity.
Go a little further
Write a decorator retry(times) that takes an argument and retries a function that raises an exception.