1. What is a Decorator?
Definition:
A decorator is a function that takes another function as input and extends or alters its behavior without modifying its actual code. It’s a powerful tool for enhancing code reuse and separating concerns.
Use Case:
Decorators are commonly used for:
Logging: Automatically logging information about function calls.
Authentication: Checking if a user is authorized to perform an action.
Memoization: Caching results of expensive function calls to improve performance.
2. How Decorators Work
Function Decorators: A decorator wraps a function to modify or extend its behavior. The syntax for using a decorator is the @decorator_name
syntax placed above the function definition.
Syntax Example:
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
Explanation:
Here, my_decorator
is applied to say_hello
. The wrapper
function inside the decorator adds behavior before and after the execution of say_hello
.
3. Common Use Cases
Logging Decorator Example:
import logging
logging.basicConfig(level=logging.INFO)
def log_decorator(func):
def wrapper(*args, **kwargs):
logging.info(f"Function {func.__name__} called with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
logging.info(f"Function {func.__name__} returned {result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
add(5, 3)
Output:
INFO:root:Function add called with args: (5, 3), kwargs: {}
INFO:root:Function add returned 8
Explanation:
This logging decorator logs the function name, its arguments, and the result of the function call.
Authorization Decorator Example:
def require_permission(permission):
def decorator(func):
def wrapper(user, *args, **kwargs):
if user.has_permission(permission):
return func(user, *args, **kwargs)
else:
raise PermissionError("User does not have the required permission.")
return wrapper
return decorator
class User:
def __init__(self, name, permissions):
self.name = name
self.permissions = permissions
def has_permission(self, permission):
return permission in self.permissions
@require_permission('admin')
def delete_user(user):
print(f"User {user.name} deleted.")
admin_user = User('Admin', ['admin'])
regular_user = User('Regular', [])
delete_user(admin_user) # User Admin deleted.
delete_user(regular_user) # Raises PermissionError
Explanation:
This authorization decorator checks if a user has the required permission before allowing the function to execute.
Memoization Decorator Example:
def memoize(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def expensive_computation(n):
print("Computing...")
return n * n
print(expensive_computation(4)) # Computes and caches
print(expensive_computation(4)) # Uses cache
Output:
Computing...
16
16
Explanation:
This memoization decorator caches the results of expensive computations to avoid redundant calculations.
4. Creating Your Own Decorators
Basic Decorator:
def basic_decorator(func):
def wrapper(*args, **kwargs):
print("Doing something before the function")
result = func(*args, **kwargs)
print("Doing something after the function")
return result
return wrapper
@basic_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Output:
Doing something before the function
Hello, Alice!
Doing something after the function
Explanation:
This basic decorator adds behavior before and after the greet
function.
Decorator with Arguments:
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Output:
Hello, Alice!
Hello, Alice!
Hello, Alice!
Explanation:
This decorator allows specifying the number of times a function should be repeated.
Class-Based Decorator:
class ClassDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Doing something before the function")
result = self.func(*args, **kwargs)
print("Doing something after the function")
return result
@ClassDecorator
def say_hi():
print("Hi!")
say_hi()
Output:
Doing something before the function
Hi!
Doing something after the function
Explanation:
This class-based decorator provides a more flexible way to define decorators, especially if you need to maintain state.
5. Decorator Chaining
You can apply multiple decorators to a single function by stacking them:
def upper_case_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
def exclamation_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result + "!!!"
return wrapper
@upper_case_decorator
@exclamation_decorator
def greet(name):
return f"Hello, {name}"
print(greet("Alice"))
Output:
HELLO, ALICE!!!
Explanation:
In this example, the greet
function is first decorated with exclamation_decorator
and then with upper_case_decorator
. The result is a greeting in uppercase followed by exclamation marks.
6. Real-World Use Cases and Best Practices
When to Use Decorators:
Code Reusability: Avoid duplicating code by applying common functionalities across multiple functions.
Separation of Concerns: Keep your core logic separate from auxiliary functionality like logging or validation.
Clean and Readable Code: Decorators help keep code more organized and readable.
Performance Implications:
Overhead: Decorators add a layer of function calls which can impact performance. Use them judiciously.
Debugging: Be aware that decorators can sometimes obscure function names and arguments, making debugging slightly more complex.
Interview Questions and Answers
- What is a decorator in Python?
* A decorator is a function that takes another function and extends or alters its behavior without modifying its code.
- What are some common use cases for decorators?
* Logging, authentication, memoization, and validation.
- How do you apply multiple decorators to a single function?
* By stacking them above the function definition using the `@decorator_name` syntax.
- What is the difference between a function-based decorator and a class-based decorator?
* A function-based decorator is defined using nested functions, while a class-based decorator uses a class with a `__call__` method to maintain state.
- Can decorators accept arguments? If so, how?
* Yes, decorators can accept arguments by defining an outer function that takes the arguments and returns the actual decorator function.
Top comments (0)