Let’s go beyond the basics to understand decorators in depth. Decorators are not just about "extra layers" but offer a sophisticated way to add functionality to functions dynamically, making them highly adaptable and powerful.
1. What is a Decorator?
In essence, a decorator is a higher-order function—a function that accepts another function as an argument, adds functionality, and returns a new function. This allows us to "decorate" an original function with added capabilities, leaving the original function unaltered.
Syntax Recap:
@decorator_name
def my_function():
pass
Using @decorator_name
before my_function
is shorthand for:
my_function = decorator_name(my_function)
2. Building a Basic Decorator
Let’s construct a simple decorator that logs when a function is called.
def log_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def greet(name):
print(f"Hello, {name}!")
greet("Alice") # Outputs: Calling greet, then Hello, Alice!
-
log_call
is a decorator that wrapsgreet
. -
*args
and `kwargs`** ensure it works with any number of positional or keyword arguments.
3. Real-World Use Cases
Decorators are commonly used for:
- Access Control: e.g., checking user permissions.
- Caching: Storing results of expensive function calls.
- Retry Mechanisms: Automatically retrying a function on failure.
- Input Validation: Checking arguments before a function runs.
4. Decorators with Arguments
Sometimes, decorators need extra parameters. In these cases, we add an outer function to pass parameters to the decorator.
Example:
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello!")
say_hello() # Outputs "Hello!" three times
Here, repeat
is a decorator factory that generates a decorator based on the times
argument.
5. Stacking Decorators
You can stack multiple decorators on a single function, creating a powerful chain of behaviors.
Example:
def make_bold(func):
def wrapper():
return "<b>" + func() + "</b>"
return wrapper
def make_italic(func):
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@make_bold
@make_italic
def greet():
return "Hello!"
print(greet()) # Outputs: <b><i>Hello!</i></b>
Stacking @make_bold
and @make_italic
wraps greet
in both bold and italic tags.
6. Preserving Metadata with functools.wraps
When decorating functions, you’ll often want the original function’s metadata (like its name and docstring) preserved. Use functools.wraps
to make sure your wrapper doesn’t overwrite these details.
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@wraps(func)
ensures that func
's name and docstring are retained.
7. Decorators in Classes
Decorators aren’t just for standalone functions; they can also be used with class methods.
Example:
def require_auth(method):
@wraps(method)
def wrapper(self, *args, **kwargs):
if not self.is_authenticated:
raise PermissionError("Authentication required.")
return method(self, *args, **kwargs)
return wrapper
class User:
def __init__(self, authenticated):
self.is_authenticated = authenticated
@require_auth
def access_dashboard(self):
return "Accessing dashboard!"
user = User(authenticated=True)
print(user.access_dashboard()) # Outputs: Accessing dashboard!
The require_auth
decorator checks if the user is authenticated before allowing access to the access_dashboard
method.
Conclusion: Supercharge Your Code with Decorators
Decorators are an invaluable part of Python, allowing you to enhance, modify, and control function behavior in a flexible and reusable way. They make your code more expressive, modular, and elegant. With decorators, you're not just adding functionality—you’re refining and enriching your codebase.
Top comments (0)