🎨 Advanced Decorators in Python
Decorators are a powerful and expressive feature in Python that allows you to modify the behavior of functions or classes. While basic decorators are common, advanced techniques unlock even more potential.
🧠 What are Decorators?
At their core, decorators are functions that take another function and extend its behavior without explicitly modifying it.
@my_decorator
def my_function():
pass
Is syntactic sugar for:
def my_function():
pass
my_function = my_decorator(my_function)
🧩 Decorators with Arguments
To pass arguments to your decorator, you need to add another layer of wrapping.
import functools
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
greet("World")
# Output:
# Hello World
# Hello World
# Hello World
🏗 Class Decorators
Decorators can also be applied to classes. This is useful for adding functionality to every method or modifying class attributes.
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self):
print("Loading database...")
db1 = DatabaseConnection()
db2 = DatabaseConnection()
# "Loading database..." prints only once
print(db1 is db2) # True
💾 Stateful Decorators
You can use a class as a decorator to maintain state.
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__!r}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
# Output:
# Call 1 of 'say_hello'
# Hello!
# Call 2 of 'say_hello'
# Hello!
🔄 Nested Decorators
You can stack multiple decorators on a single function. They are applied from bottom to top.
@debug
@do_twice
def greet(name):
print(f"Hello {name}")
Equivalent to: greet = debug(do_twice(greet))
📝 Summary
- ✅ Decorators with Arguments: Need three levels of nested functions.
- ✅ Class Decorators: Can modify entire classes or manage instances (Singleton).
- ✅ Stateful Decorators: Use classes with
__call__to keep state. - ✅ functools.wraps: Always use this to preserve metadata (name, docstring) of the original function.
Created with ❤️ by Pynfinity