A decorator is a function that takes another function and returns a new one. You use it to add behaviour—like logging or timing—to existing functions without modifying their code.
The basic structure
def my_decorator(func):
def wrapper():
print('Before')
func()
print('After')
return wrapper
@my_decorator
def say_hi():
print('Hi!')
say_hi()The
wrapper function runs the original code, plus your extra logic. The @ syntax simply reassigns the name to point at the wrapper.Working with any arguments
def my_decorator(func):
def wrapper(*args, **kwargs):
print('Before')
result = func(*args, **kwargs)
print('After')
return result
return wrapperPreserving metadata
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapperWithout
@functools.wraps, reading __name__ on a decorated function would give 'wrapper'. With it, you get back the real name. This matters for debugging and introspection tools.Keeping state on the wrapper
import functools
def counted(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
wrapper.calls += 1 # the wrapper can see itself
return func(*args, **kwargs)
wrapper.calls = 0 # set the starting value once
return wrapper
@counted
def ping():
return 'pong'
ping()
ping()
print(ping.calls)
# 2
A function in Python is an ordinary object, so you can attach attributes to it. Set the starting value once, after
wrapper is defined but before you return it, and update it inside the wrapper. Because the decorated name now points at wrapper, reading ping.calls from outside reads that same attribute.