If you have been programming in Python for a while, you have probably seen the “@” symbol placed above function definitions. These are called Decorators.
Decorators can feel confusing for beginners, but they are extremely powerful. In simple terms, a decorator allows you to add new functionality to an existing function without modifying its original code.
In this guide, we will break down how decorators work and look at 3 real-world examples you can use in your projects today.
What is a Python Decorator?
In Python, functions are first-class objects. This means functions can be:
-
Passed as arguments to other functions.
-
Returned from other functions.
-
Assigned to variables.
A decorator is simply a function that takes another function as an argument, wraps some extra behavior around it, and returns the modified function.
1. Execution Time Measurement Decorator
When optimizing code, you often need to know how long a specific function takes to run. Instead of writing timing logic inside every function, you can create a reusable “@timer” decorator.
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f“Execution time for {func.__name__}:
{end_time – start_time:.4f} seconds“)
return result
return wrapper
@timer
def heavy_calculation():
# Simulating a slow task
time.sleep(2)
return “Task Completed“
# Calling the function
heavy_calculation()
Enter fullscreen mode
Exit fullscreen mode
2. Function Logging Decorator
In production applications, logging function calls with their arguments helps immensely with debugging.
def log_function_call(func):
def wrapper(*args, **kwargs):
print(f“[LOG]: Calling ‘{func.__name__}‘ with arguments {args}“)
result = func(*args, **kwargs)
print(f“[LOG]: ‘{func.__name__}‘ returned {result}“)
return result
return wrapper
@log_function_call
def add_numbers(a, b):
return a + b
add_numbers(10, 20)
Enter fullscreen mode
Exit fullscreen mode
3. Simple Authentication Check Decorator
Decorators are widely used in web frameworks like Flask and FastAPI to restrict access to specific routes based on user roles or login status.
def require_authentication(func):
def wrapper(user, *args, **kwargs):
if not user.get(“is_authenticated“):
print(“Access Denied: Please log in first.“)
return None
return func(user, *args, **kwargs)
return wrapper
@require_authentication
def view_dashboard(user):
print(f“Welcome to your dashboard, {user[‘name‘]}!“)
# Simulated user objects
guest_user = {“name“: “Alice“, “is_authenticated“: False}
logged_in_user = {“name“: “Shagun“, “is_authenticated“: True}
view_dashboard(guest_user) # Output: Access Denied
view_dashboard(logged_in_user) # Output: Welcome to your dashboard, Shagun!
Enter fullscreen mode
Exit fullscreen mode
Key Takeaways
-
DRY Principle: Decorators help you keep your code clean by avoiding repeated code for tasks like logging, timing, and security checks.
-
Flexibility: Use *args and *kwargs inside the wrapper function so your decorator works with any number of positional or keyword arguments.
