Python - Decorators in Python — Detailed Explanation
A decorator in Python is a design pattern that allows you to modify or extend the behavior of a function or method without changing its actual code. It is essentially a function that takes another function as input and returns a new function with added functionality.
1. Basic Idea
In Python, functions are first-class objects, meaning:
-
You can pass them as arguments
-
You can return them from other functions
-
You can assign them to variables
A decorator uses this property.
2. Simple Example Without Decorator Syntax
def greet():
print("Hello")
def my_decorator(func):
def wrapper():
print("Before function execution")
func()
print("After function execution")
return wrapper
greet = my_decorator(greet)
greet()
Output:
Before function execution
Hello
After function execution
Here:
-
my_decoratortakesgreetas input -
It wraps it inside another function (
wrapper) -
Adds extra behavior before and after execution
3. Using Decorator Syntax (@)
Python provides a cleaner way using @ syntax.
def my_decorator(func):
def wrapper():
print("Before function execution")
func()
print("After function execution")
return wrapper
@my_decorator
def greet():
print("Hello")
greet()
This is equivalent to:
greet = my_decorator(greet)
4. Decorators with Arguments
If the original function takes parameters, the wrapper must accept them.
def my_decorator(func):
def wrapper(name):
print("Before execution")
func(name)
print("After execution")
return wrapper
@my_decorator
def greet(name):
print(f"Hello {name}")
greet("Alice")
5. Using *args and **kwargs
To handle any number of arguments:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before execution")
result = func(*args, **kwargs)
print("After execution")
return result
return wrapper
This makes the decorator reusable for any function.
6. Returning Values from Decorated Functions
def my_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(2, 3)) # Output: 10
7. Real-World Use Cases
Decorators are widely used in real applications:
a) Logging
def log(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
b) Authentication (Web apps)
-
Check if a user is logged in before running a function
c) Timing Functions
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print("Time:", end - start)
return wrapper
d) Caching (memoization)
8. Preserving Function Metadata
When you use decorators, the original function name and docstring get replaced.
To fix this, use functools.wraps:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
9. Decorators with Arguments (Advanced)
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello")
say_hello()
10. Key Points to Remember
-
A decorator is a function that wraps another function
-
It adds functionality without modifying original code
-
Uses closures (functions inside functions)
-
Common in frameworks like Flask, Django
-
Improves code reusability and cleanliness
Summary
Decorators provide a powerful way to:
-
Add functionality dynamically
-
Keep code clean and modular
-
Avoid repetition
They are especially important in advanced Python programming and are heavily used in real-world applications such as web development, logging systems, and performance optimization.