Mastering Python Decorators: A Key Skill Every Developer Should Know in 2025
Introduction
1 . What are Python Decorators?
2 . Basic Syntax of a Decorator
3 . Why Use Decorators?
4 . Handling Functions with Arguments
5 . Preserving Original Function Metadata
6 . Common Use Cases of Decorators
7 . Decorators with Arguments
8 . Class Method Decorators
9 . Built-in Decorators in Python
10 . Chaining Multiple Decorators
11 . Debugging Decorated Functions
12 . Real-World Example: Timing Function Execution
Conclusion
Bonus Tip
Python decorators are one of the most powerful features in the language. They allow you to modify or enhance the behavior of functions or methods without changing their actual code. Decorators help keep your code clean, reusable, and easier to maintain. Whether you are building web apps, APIs, or automation scripts, understanding decorators will improve your coding skills significantly.
In this blog, we’ll cover everything you need to know about decorators in an easy-to-understand way with practical examples.
A decorator is a function that takes another function as an argument, wraps some additional logic around it, and returns a new function with the enhanced behavior. They are often used for logging, authorization, caching, and more.
Think of decorators like gift wrappers — you keep the original gift (function) but add something extra around it.
Here’s the simplest form of a decorator:
def my_decorator(func):
def wrapper():
print("Before the function runs")
func()
print("After the function runs")
return wrapper
@my_decorator
def greet():
print("Hello!")
greet()
Output -
Before the function runs
Hello!
After the function runs
Using @my_decorator is equivalent to greet = my_decorator(greet).
Code Reusability: Write common logic once and reuse it by decorating many functions.
Separation of Concerns: Keep your main function logic clean and separate from things like logging or access control.
Cleaner Code: Avoid repeating code snippets for the same purpose in multiple places.
Most real-world functions accept parameters, so decorators should too:
def decorator_with_args(func):
def wrapper(*args, **kwargs):
print(f"Function {func.__name__} is called with args: {args} kwargs: {kwargs}")
return func(*args, **kwargs)
return wrapper
@decorator_with_args
def add(a, b):
return a + b
print(add(5, 7))
When you decorate a function, Python replaces it with the wrapper function. This causes loss of metadata like the function’s name and docstring. Use functools.wraps to preserve these:
import functools
def decorator_preserve_metadata(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Calling decorated function")
return func(*args, **kwargs)
return wrapper
@decorator_preserve_metadata
def multiply(x, y):
"""Multiply two numbers"""
return x * y
print(multiply.__name__) # multiply
print(multiply.__doc__) # Multiply two numbers
Sometimes decorators themselves need arguments:
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello!")
say_hello()
You can decorate methods inside classes too:
def method_decorator(func):
def wrapper(self, *args, **kwargs):
print(f"Calling {func.__name__} method")
return func(self, *args, **kwargs)
return wrapper
class Greeter:
@method_decorator
def greet(self):
print("Hi from class!")
g = Greeter()
g.greet()
Python has several built-in decorators:
You can stack multiple decorators:
def decorator1(func):
def wrapper():
print("Decorator 1")
func()
return wrapper
def decorator2(func):
def wrapper():
print("Decorator 2")
func()
return wrapper
@decorator1
@decorator2
def say():
print("Hello!")
say()
Output:
Decorator 1
Decorator 2
Hello!
If a decorated function misbehaves, check that the decorator properly handles arguments and preserves metadata using functools.wraps.
Measure how long a function takes to run:
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} executed in {end - start:.4f} seconds")
return result
return wrapper
@timer
def waste_time(num):
for _ in range(num):
pass
waste_time(1000000)
Decorators are a powerful tool that helps Python developers write cleaner, more modular, and reusable code. Mastering them improves your code quality and productivity. Keep experimenting with decorators to apply them in real-world projects like logging, authentication, caching, and more.
Explore popular frameworks like Flask and Django, they use decorators extensively for routing, middleware, and view protections.
Backend Developer & Community Contributor
David is a full-stack developer and open-source advocate focused on Node.js and PHP ecosystems. He contributes regularly to developer forums and writes tutorials for aspiring programmers.