📌 Introduction
In Python, decorators are powerful tools that let you add extra functionality to functions, methods, or classes without changing their original code. Think of them as wrappers. they “wrap” around an existing function and run extra code before or after the original function.
They’re widely used in real-world applications, especially in frameworks like Flask and Django, for tasks like logging, authentication, performance measurement, and access control.
⚙️ How Decorators Work
Python treats functions as first-class objects, meaning you can store them in variables, pass them as arguments, and return them from other functions. This flexibility allows decorators to work their magic.
A decorator:
- Takes a function (or class) as input.
- Creates a wrapper function that adds new behavior.
- Returns that wrapper instead of the original function.
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!")
say_hello()
Output:
Before the function runs
Hello!
After the function runs
Here, my_decorator is shorthand for:
say_hello = my_decorator(say_hello)
🧠 First-Class Functions & Higher-Order Functions
- First-class functions: Functions can be stored in variables, passed as arguments, or returned from other functions.
- Higher-order functions: Functions that take other functions as arguments or return them.
Example:
def apply(func, value):
return func(value)
def square(x):
return x * x
print(apply(square, 5)) # Output: 25
Decorators are higher-order functions because they take a function, modify it, and return a new one.
📚 Real-World Uses of Decorators
- Logging: Record details each time a function runs.
- Authentication: Check if a user is logged in.
- Performance measurement: Track execution time.
- Access control: Restrict certain functions.
- Caching: Store results to avoid recomputation.
Example: Measuring Execution Time:
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Execution time: {end - start} seconds")
return result
return wrapper
@timing_decorator
def slow_function():
time.sleep(2)
print("Done!")
slow_function()
🛠 Advanced Decorators, with Arguments
Sometimes you need decorators that take their own arguments.
Example:
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 greet(name):
print(f"Hello, {name}!")
greet("Baibhav")
Output:
Hello, Baibhav!
Hello, Baibhav!
Hello, Baibhav!
🏛 Method Decorators (Inside Classes)
When decorating methods, remember the first parameter is always self.
Example:
def method_decorator(func):
def wrapper(self, *args, **kwargs):
print("Before method")
result = func(self, *args, **kwargs)
print("After method")
return result
return wrapper
class MyClass:
@method_decorator
def say_hi(self):
print("Hi!")
obj = MyClass()
obj.say_hi()
🏷 Class Decorators
Decorators can also modify entire classes.
Example:
def add_class_name(cls):
cls.class_name = cls.__name__
return cls
@add_class_name
class Person:
pass
print(Person.class_name)
# Output: Person
⚡ Common Built-in Decorators
- staticmethod: Defines a method that doesn’t use self.
- classmethod: Defines a method that operates on the class itself.
- property: Makes a method behave like an attribute (with optional setters/getters).
Example: property
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value >= 0:
self._radius = value
else:
raise ValueError("Radius cannot be negative")
c = Circle(5)
print(c.radius) # 5
c.radius = 10
print(c.radius) # 10
🔗 Chaining Decorators
You can apply multiple decorators to the same function.
Example:
def decor1(func):
def inner():
return func() ** 2
return inner
def decor2(func):
def inner():
return func() * 2
return inner
@decor1
@decor2
def num():
return 10
print(num())
# Output: 400
📌 Summary
Python decorators are functions that modify or enhance other functions, methods, or classes without altering their original code. They rely on first-class functions and higher-order functions, allowing flexible, reusable, and clean code. From simple logging to complex argument-based decorators, they are widely used in real-world applications and frameworks. Decorators can be applied to functions, methods, and classes, and Python even provides built-in decorators like staticmethod, classmethod, and property. You can also chain multiple decorators for layered functionality, making them an essential tool for any Python developer.