Decorators
16 min
Functions in Python are values — you can pass one as an argument, return one from another function, or wrap one to add behavior. A decorator is a function that takes a function and returns a new (usually wrapped) function.
def shout(func):
def wrapper(name):
return func(name).upper()
return wrapper
def greet(name):
return f"hello, {name}"
greet = shout(greet) # manually "decorating"
print(greet("ada")) # HELLO, ADA
@shout above 'def greet' is exactly equivalent to writing 'greet = shout(greet)' right after defining it — pure syntax sugar, nothing magic.
Real decorators need to wrap functions with ANY signature, so the wrapper
almost always uses *args, **kwargs to pass everything through unchanged:
def log_call(func):
def wrapper(*args, **kwargs):
print(f"calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
That's the shape you'll reuse for almost every decorator you write: accept anything, do something extra, then call the original function with exactly what was passed in.
Write a decorator `double_result(func)` that wraps `func` so the wrapped function's return value is doubled.
Write a decorator `require_positive(func)` that wraps `func` so that if any positional argument is negative, the wrapper returns the string 'invalid' instead of calling `func`.
What does writing @my_decorator directly above a function definition do?
You can write a decorator that wraps a function's behavior, using *args/**kwargs to pass arguments through unchanged.