๐จ 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