One interesting and powerful feature in Python is the decorator. Decorators allow you to modify the behavior of functions or classes in a very flexible and reusable way, without changing their actual code. This makes them extremely useful for logging, enforcing access control, instrumentation, caching, and more.
Example: Using a Decorator for Logging
Let's say we want to log when a function is called. Instead of adding logging code inside every function, we can use a decorator.
Step 1: Define the Decorator
A decorator is simply a function that takes another function and extends its behavior.
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__} with arguments {args} and {kwargs}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} returned {result}")
return result
return wrapper
Step 2: Use the Decorator
You can apply the decorator to any function using the `@decorator_name` syntax.
@log_decorator
def add(a, b):
return a + b
@log_decorator
def multiply(a, b):
return a * b
# Test the decorated functions
result_add = add(2, 3)
result_multiply = multiply(4, 5)
What Happens Here?
1. Defining the Decorator:
- `log_decorator` is defined to take a function `func` as an argument.
- Inside `log_decorator`, a new function `wrapper` is defined, which wraps around the original function `func`.
- The `wrapper` function logs the function name and arguments before calling `func`, and logs the result after calling `func`.
2. Applying the Decorator:
- The `@log_decorator` syntax applies the decorator to the functions `add` and `multiply`.
- When `add(2, 3)` or `multiply(4, 5)` is called, it actually calls the `wrapper` function defined inside the decorator.
Output:
Calling function add with arguments (2, 3) and {}
Function add returned 5
Calling function multiply with arguments (4, 5) and {}
Function multiply returned 20
Why is This Interesting?
Decorators are a prime example of Python’s flexibility and the power of higher-order functions. They allow for the separation of concerns, letting you add or modify functionality in a clean, reusable way without altering the core logic of the functions they decorate. This can lead to cleaner, more maintainable, and more readable code.
Advanced Use Cases
- Caching: Implementing memoization to cache expensive function calls.
- Access Control: Checking user permissions before allowing access to certain functions.
- Instrumentation: Automatically timing function execution or counting how often a function is called.
Example: Caching with a Decorator
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Test the caching decorator
print(fibonacci(100)) # Computes the 100th Fibonacci number efficiently
Here, the `lru_cache` decorator from the `functools` module caches the results of the `fibonacci` function, making subsequent calls with the same arguments return much faster.
Decorators highlight Python’s expressive syntax and its emphasis on readability and simplicity, making them a fascinating and useful feature of the language.