Understanding Decorators
A decorator is a callable Python object that takes another function as its argument and returns a new function. It’s a powerful tool for modifying the behavior of functions without altering their source code. Decorators are essentially syntactic sugar for applying functions to other functions.
Basic Decorators
Let’s start with a simple example:
def my_decorator(func):
def wrapper():
print("Something before the function")
func()
print("Something after the function")
return wrapper
@my_decorator
def my_function():
print("This is the original function")
my_function()
Output:
Something before the function
This is the original function
Something after the function
The @my_decorator
syntax is equivalent to my_function = my_decorator(my_function)
.
Decorators with Arguments
Decorators can also take arguments:
def decorator_with_args(arg1, arg2):
def inner_decorator(func):
def wrapper(*args, **kwargs):
print("Decorator with arguments:", arg1, arg2)
func(*args, **kwargs)
return wrapper
return inner_decorator
@decorator_with_args("hello", "world")
def my_function(x, y):
print("x:", x, "y:", y)
my_function(3, 4)
Output:
Decorator with arguments: hello world
x: 3 y: 4
Decorators and Functions with Arguments
To handle functions with arguments, the wrapper function must accept arbitrary arguments using *args
and **kwargs
.
Chaining Decorators
Multiple decorators can be applied to a single function:
def decorator1(func):
# ...
def decorator2(func):
# ...
@decorator1
@decorator2
def my_function():
# ...
The decorators are applied from the innermost to the outermost.
Practical Use Cases
Decorators have many practical applications:
Logging
import logging
def log_decorator(func):
def wrapper(*args, **kwargs):
logging.info(f"Calling {func.__name__}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} returned {result}")
return result
return wrapper
Timing
import time
def time_it(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.2f} seconds")
return result
return wrapper
Caching
def cache(func):
cache_dict = {}
def wrapper(*args):
if args in cache_dict:
return cache_dict[args]
result = func(*args)
cache_dict[args] = result
return result
return wrapper
Authorization
def authorized(roles):
def decorator(func):
def wrapper(*args, **kwargs):
if current_user.role in roles:
return func(*args, **kwargs)
else:
raise PermissionError("Unauthorized")
return wrapper
return decorator
Class Decorators
Decorators can also be applied to classes:
def class_decorator(cls):
class Wrapper:
def __init__(self, *args, **kwargs):
self.wrapped = cls(*args, **kwargs)
def __getattr__(self, name):
return getattr(self.wrapped, name)
return Wrapper
@class_decorator
class MyClass:
# ...
Decorators and Metaclasses
Decorators and metaclasses are closely related. Metaclasses can be used to create decorators, and decorators can be used within metaclasses.
Conclusion
Decorators are a powerful and versatile tool in Python. They allow you to modify function behavior without altering the original code, promoting code reusability and maintainability. By understanding the core concepts and applying decorators effectively, you can write more elegant and efficient Python code.