Python Decorators – The Complete Guide with Deep Explanation


1. What is a Decorator?
In Python, functions are first-class citizens, meaning:
They can be stored in variables.
Passed as arguments.
Returned from other functions.
A decorator is simply a function that takes another function and adds extra behavior to it without changing its source code.
Basic Example
def my_decorator(func):
def wrapper():
print("Before the function runs")
func()
print("After the function runs")
return wrapper
@my_decorator
def say_hello():
print("Hello, World!")
say_hello()
Output:
Before the function runs
Hello, World!
After the function runs
2. How Decorators Work
When you write:
@my_decorator
def say_hello():
pass
It’s actually:
say_hello = my_decorator(say_hello)
That means the original say_hello
is replaced by the wrapper function.
3. Decorators with Arguments
If your function has parameters, the decorator’s wrapper should accept them.
def log_arguments(func):
def wrapper(*args, **kwargs):
print(f"Arguments: {args} {kwargs}")
return func(*args, **kwargs)
return wrapper
@log_arguments
def add(a, b):
return a + b
print(add(3, 5))
Output:
Arguments: (3, 5) {}
8
4. Preserving Function Metadata
Without care, decorators overwrite the original function’s name & docstring.
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""This is an example function."""
pass
print(example.__name__) # wrapper, not example
Solution: Use functools.wraps
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
5. Real-World Uses of Decorators
Logging
Authentication checks
Caching results
Timing function execution
Access control
Example: Timing a Function
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
time.sleep(2)
print("Done!")
slow_function()
Output:
Done!
slow_function took 2.0003 seconds
6. Class-Based Decorators
You can also create decorators using classes.
class Repeat:
def __init__(self, times):
self.times = times
def __call__(self, func):
def wrapper(*args, **kwargs):
for _ in range(self.times):
func(*args, **kwargs)
return wrapper
@Repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Naeem")
Output:
CopyEditHello, Naeem!
Hello, Naeem!
Hello, Naeem!
7. Summary
Decorators wrap functions to add functionality.
Use
@decorator_name
syntax.functools.wraps
keeps metadata.Useful for logging, authentication, performance tracking, etc.
Subscribe to my newsletter
Read articles from M Naeem BanGash directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
