Decorators are callables that take a function and return another function (or callable), usually to add behavior—logging, timing, validation, retries—without changing the original function’s code. The @decorator syntax is shorthand for “replace this function with the result of calling the decorator on it.” Understanding closures and *args and **kwargs helps when writing decorators that work with any function signature.
What they are
A decorator is a function that accepts a function and returns a new function. When you write @my_decorator above def f(): ..., Python does f = my_decorator(f). Calls to f() then run the returned wrapper, which can do work before and after (and around) calling the original function.
Simple decorator (no arguments)
def my_decorator(func):
def wrapper():
print("Before function")
func()
print("After function")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# Before function
# Hello!
# After function
Here wrapper is a closure that captures func. Returning wrapper makes say_hello refer to it, so say_hello() runs wrapper().
Decorator that preserves the original function’s arguments
To wrap functions that take arguments, the wrapper must accept and forward them. Use *args, **kwargs and return the original function’s return value:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Calling function...")
result = func(*args, **kwargs)
print("Function finished")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(3, 5)) # Calling... / Function finished / 8
Common use: timing
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.2f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "Done"
slow_function() # slow_function took 1.00s
When to use decorators
- Cross-cutting behavior: logging, timing, retries, or validation applied to many functions without repeating code.
- Wrappers that must work with any signature: use
*args, **kwargsand returnfunc(*args, **kwargs). - Framework hooks: many libraries use decorators for routes, auth, or registration (e.g. Flask
@app.route).
Next steps: Core Concepts · Closures · *args and **kwargs · Functions