What Are Decorators in Python
If you've written Python for more than a week, you've likely seen the @ symbol above a function. That's a decorator — a powerful and elegant programming tool.
Decorators in Python let you "wrap" one function inside another. They modify or extend a function's behavior without changing its source code. Decorators are used everywhere: in Flask, Django, FastAPI, pytest, and even in Python's standard library.
How Decorators Work
Decorator Basics
A decorator is a function that takes another function and returns a new function or a modified version of the original.
def decorator(func):
def wrapper():
print("Before call")
func()
print("After call")
return wrapper
@decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Before call
Hello!
After call
The @decorator syntax is syntactic sugar for:
say_hello = decorator(say_hello)
How Decorators Work Internally
To understand decorators in Python, you need to know these key concepts:
- Functions are first-class objects in Python
- Functions can be passed as arguments to other functions
- You can define functions inside other functions
- Closures are a built-in mechanism
Universal Decorator Template
The minimal decorator template looks like this:
def decorator(func):
def wrapper(*args, **kwargs):
# Actions before the function runs
result = func(*args, **kwargs)
# Actions after the function runs
return result
return wrapper
Practical Decorator Examples
Timing Decorator
import time
def timing(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
execution_time = time.time() - start
print(f"{func.__name__} took {execution_time:.4f} sec")
return result
return wrapper
@timing
def slow_func():
time.sleep(1)
return "Operation complete"
slow_func()
This decorator is great for profiling function performance and optimizing your code.
Authorization Decorator
def require_admin(func):
def wrapper(user, *args, **kwargs):
if user != "admin":
raise PermissionError("Access denied")
return func(user, *args, **kwargs)
return wrapper
@require_admin
def delete_database(user):
print("Database deleted!")
return True
delete_database("admin") # Successful execution
This decorator enhances application security by controlling access to critical functions.
Logging Decorator
import logging
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"Calling {func.__name__} with args {args}, {kwargs}")
try:
result = func(*args, **kwargs)
logging.info(f"Function {func.__name__} completed successfully")
return result
except Exception as e:
logging.error(f"Error in {func.__name__}: {e}")
raise
return wrapper
Decorators with Arguments
Creating a Decorator Factory
When a decorator needs to accept parameters, you create a decorator factory:
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
results = []