Introduction
Decorators allow developers to modify or extend the behavior of functions and methods without changing their original code. They provide a clean and reusable way to add functionality such as logging, authentication, timing, validation, caching, and access control.
For example, imagine you have multiple functions in an application and want to log whenever each function is executed. Instead of writing logging code inside every function, you can create a decorator and apply it wherever needed.
What are Python Decorators?
A decorator is a function that takes another function as an argument, adds some functionality, and returns a modified function.
In simple words:
A decorator wraps another function to extend its behavior without modifying the original function.
Example:
def decorator_function(func):
def wrapper():
print("Before Function")
func()
print("After Function")
return wrapper
The decorator adds functionality before and after the original function executes.
Functions are First-Class Objects
To understand decorators, you must first understand that functions are first-class objects in Python.
This means functions can:
- Be assigned to variables
- Be passed as arguments
- Be returned from other functions
Example:
def greet():
print("Hello")
message = greet
message()
Output:
This capability makes decorators possible.
Syntax
General syntax:
def decorator(func):
def wrapper():
# Extra functionality
func()
return wrapper
Applying the decorator:
@decorator
def hello():
print("Hello World")
This is equivalent to:
hello = decorator(hello)
Creating a Simple Decorator
Example:
def my_decorator(func):
def wrapper():
print("Before Function")
func()
print("After Function")
return wrapper
Applying:
@my_decorator
def greet():
print("Welcome")
Usage:
greet()
Output:
Welcome
After Function
Understanding How Decorators Work
Let’s break it down:
Step 1
Original function:
def greet():
print("Welcome")
Step 2
Decorator receives the function:
my_decorator(greet)
Step 3
Decorator returns the wrapper function.
Step 4
When called:
greet()
Python actually executes:
wrapper()
which internally calls the original function.
Decorators with Arguments
Functions often accept parameters.
Example:
def decorator(func):
def wrapper(name):
print("Before Function")
func(name)
return wrapper
Applying:
@decorator
def greet(name):
print(
f"Hello {name}"
)
Usage:
greet("John")
Output:
Before Function
Hello John
Using *args and **kwargs
To support any number of arguments, use:
*args
**kwargs
Example:
def decorator(func):
def wrapper(
*args,
**kwargs
):
print("Executing")
return func(
*args,
**kwargs
)
return wrapper
Applying:
@decorator
def add(a, b):
return a + b
Usage:
print(add(10, 20))
Output:
30
Returning Values from Decorators
Always return the original function’s result when needed.
Example:
def decorator(func):
def wrapper(*args):
result = func(*args)
return result
return wrapper
Usage:
@decorator
def multiply(a, b):
return a * b
print(
multiply(4, 5)
)
Output:
Multiple Decorators
Python allows stacking decorators.
Example:
def decorator1(func):
def wrapper():
print("Decorator 1")
func()
return wrapper
def decorator2(func):
def wrapper():
print("Decorator 2")
func()
return wrapper
Applying:
@decorator1
@decorator2
def greet():
print("Hello")
Output:
Decorators are applied from bottom to top.
Decorators with Parameters
Decorators themselves can accept arguments.
Example:
def repeat(times):
def decorator(func):
def wrapper():
for i in range(times):
func()
return wrapper
return decorator
Usage:
@repeat(3)
def hello():
print("Hello")
Output:
Hello
Hello
Real-Life Examples:
1. Logging Decorator
def logger(func):
def wrapper(*args):
print(
f"Calling {func.__name__}"
)
return func(*args)
return wrapper
Applying:
@logger
def add(a, b):
return a + b
Usage:
print(add(5, 3))
Output:
8
Useful for debugging applications.
2. Authentication Decorator
def authenticate(func):
def wrapper(user):
if user == "admin":
return func(user)
print("Access Denied")
return wrapper
Applying:
@authenticate
def dashboard(user):
print("Welcome Admin")
Usage:
dashboard("admin")
Output:
3. Execution Timer
import time
def timer(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(
"Time:",
end - start
)
return wrapper
Usage:
@timer
def task():
time.sleep("2")
task()
Output:
(Approximate value)
Built-in Decorators in Python
Python provides several built-in decorators.
| Decorator | Purpose |
|---|---|
| @staticmethod | Creates static methods |
| @classmethod | Creates class methods |
| @property | Creates getter methods |
| @abstractmethod | Defines abstract methods |
Example:
class Student:
@staticmethod
def show():
print("Static Method")
The @property Decorator
Example:
class Student:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
Usage:
student = Student("John")
print(student.name)
Output:
Advantages of Decorators
| Advantage | Description |
|---|---|
| Reusability | Use logic multiple times |
| Cleaner Code | Reduces duplication |
| Better Maintenance | Easier updates |
| Separation of Concerns | Keeps code organized |
| Flexibility | Modify functions dynamically |
Disadvantages of Decorators
| Disadvantage | Description |
|---|---|
| Learning Curve | Difficult for beginners |
| Debugging Complexity | Wrapped functions can be harder to trace |
| Excessive Nesting | Multiple decorators can reduce readability |
Common Mistakes
1. Forgetting to Return Wrapper
Incorrect:
def decorator(func):
def wrapper():
pass
Correct:
return wrapper
2. Not Returning Function Result
Incorrect:
def wrapper():
func()
Correct:
return func()
3. Ignoring Arguments
Incorrect:
def wrapper():
Correct:
def wrapper(
*args,
**kwargs
):
4. Misunderstanding Decorator Order
Example:
@A
@B
def func():
Equivalent to:
func = A(B(func))
5. Overusing Decorators
Not every function needs a decorator.
Use them only when they improve code organization.
Best Practices
1. Use Meaningful Names
Good:
@authenticate
@logger
@timer
Bad:
@d1
@d2
2. Support All Arguments
Use:
*args
**kwargs
for maximum flexibility.
3. Keep Decorators Focused
One decorator should perform one task.
4. Return Function Results
Always return the original function’s output if needed.
5. Use Built-in Decorators When Available
Examples:
@property
@classmethod
@staticmethod
Conclusion
Python Decorators are a powerful feature that allows developers to modify or extend the behavior of functions and methods without changing their original code.
Mastering decorators is an essential step toward becoming an advanced Python developer and writing cleaner, more professional applications.