Advanced Decorator Techniques and Use Cases
As you've seen, decorators are a powerful tool for modifying function behavior in Python. In this section, we'll explore some more advanced decorator techniques and use cases.
Decorating Classes
Decorators can also be used to modify the behavior of classes. Here's an example of a decorator that adds a logging method to a class:
def log_class_methods(cls):
class LoggedClass(cls):
def __getattribute__(self, attr):
if callable(super(LoggedClass, self).__getattribute__(attr)):
def logged_method(*args, **kwargs):
print(f"Calling method {attr}")
return super(LoggedClass, self).__getattribute__(attr)(*args, **kwargs)
return logged_method
return super(LoggedClass, self).__getattribute__(attr)
return LoggedClass
@log_class_methods
class MyClass:
def __init__(self, value):
self.value = value
def do_something(self):
print(f"Doing something with value: {self.value}")
obj = MyClass(42)
obj.do_something() ## Output: Calling method do_something
## Doing something with value: 42
In this example, the log_class_methods
decorator takes a class as an argument and returns a new class that wraps all the methods of the original class with a logging function.
Decorators with State
Decorators can also maintain state between function calls. This can be useful for caching, rate limiting, or other stateful operations. Here's an example of a decorator that caches the results of a function:
def cache(func):
cache = {}
def wrapper(*args):
if args in cache:
print("Returning cached result")
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) ## Output: Calculating fibonacci(10)
## 55
print(fibonacci(10)) ## Output: Returning cached result
## 55
In this example, the cache
decorator maintains a dictionary of function call arguments and their corresponding results. When the decorated function is called, the decorator first checks if the result is already cached, and if so, returns the cached result. Otherwise, it calculates the result and stores it in the cache for future use.
Decorator Factories
Sometimes, you may want to create decorators that can be configured with arguments. This can be achieved using a decorator factory, which is a function that returns a decorator. Here's an example:
def repeat(n):
def decorator(func):
def wrapper():
result = ""
for _ in range(n):
result += func()
return result
return wrapper
return decorator
@repeat(3)
def say_hello():
return "hello "
print(say_hello()) ## Output: hello hello hello
In this example, the repeat
function is a decorator factory that takes an argument n
and returns a decorator that wraps the original function, calling it n
times and concatenating the results.
These advanced decorator techniques demonstrate the flexibility and power of decorators in Python. By using decorators, you can create reusable, modular, and easily maintainable code that can be easily extended and customized to meet your needs.