Python > Advanced Python Concepts > Decorators > Decorators with Arguments
Decorators with Arguments: Logging Function Calls
This code snippet demonstrates how to create decorators that accept arguments. We'll build a logging decorator that logs function calls with a customizable log message.
Basic Decorator Structure
This is the fundamental structure of a decorator. It takes a function as input and returns a wrapped function. The wrapper executes code before and/or after the original function.
def my_decorator(func):
def wrapper(*args, **kwargs):
# Code to execute before calling the function
result = func(*args, **kwargs)
# Code to execute after calling the function
return result
return wrapper
Decorator with Arguments Structure
To create a decorator that accepts arguments, we introduce an extra layer of function nesting. The outer function (decorator_with_args
) accepts the arguments for the decorator. It then returns the actual decorator function (my_decorator
), which takes the function to be decorated as input. The inner wrapper
function then calls the original function.
def decorator_with_args(arg1, arg2):
def my_decorator(func):
def wrapper(*args, **kwargs):
# Code to execute before calling the function, using arg1 and arg2
result = func(*args, **kwargs)
# Code to execute after calling the function, using arg1 and arg2
return result
return wrapper
return my_decorator
Example: Logging Decorator with a Message
Here, log_calls
takes a message
argument. The returned decorator logs a message before and after the function call, including the function's name, arguments, and return value. We use @functools.wraps(func)
to preserve the original function's metadata (name, docstring, etc.). This is good practice to avoid unexpected behavior. When calling the decorated functions `add` and `multiply`, the specified log messages ('DEBUG' and 'INFO') are used.
import functools
def log_calls(message):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'{message}: Calling {func.__name__} with args {args} and kwargs {kwargs}')
result = func(*args, **kwargs)
print(f'{message}: {func.__name__} returned {result}')
return result
return wrapper
return decorator
@log_calls('DEBUG')
def add(x, y):
return x + y
@log_calls('INFO')
def multiply(x, y):
return x * y
print(add(5, 3))
print(multiply(5, 3))
Output of the Example
The output of the code will be:
DEBUG: Calling add with args (5, 3) and kwargs {}
DEBUG: add returned 8
8
INFO: Calling multiply with args (5, 3) and kwargs {}
INFO: multiply returned 15
15
Real-Life Use Case
Decorators with arguments are widely used in frameworks and libraries for tasks like:
Best Practices
@functools.wraps
: This preserves the original function's metadata, making debugging and introspection easier.wrapt
provide more advanced decorator features and can simplify complex use cases.
Interview Tip
When discussing decorators in an interview, demonstrate a clear understanding of their structure and purpose. Be prepared to explain how they work under the hood and provide real-world examples of their usage. Mentioning @functools.wraps
shows that you understand best practices.
When to Use Them
Use decorators when you need to add functionality to multiple functions in a consistent and reusable way. They are particularly useful for cross-cutting concerns like logging, authentication, and caching.
Memory Footprint
Decorators introduce a slight overhead because they create a new function (the wrapper). However, this overhead is typically negligible unless you're decorating a very large number of functions or calling the decorated functions extremely frequently. The memory footprint is primarily determined by the code within the decorator itself.
Alternatives
Alternatives to decorators include:
Pros
Cons
@functools.wraps
.
FAQ
-
Why use
@functools.wraps
?
@functools.wraps
preserves the original function's metadata (name, docstring, etc.). Without it, the decorated function will appear to have the name and docstring of the wrapper function, making debugging and introspection more difficult. It's best practice to always include it when defining decorators. -
Can I apply multiple decorators to a single function?
Yes, you can apply multiple decorators to a single function. The decorators are applied from top to bottom, meaning the decorator closest to the function definition is applied first. -
How do I access the arguments passed to the decorator within the decorated function?
The arguments passed to the decorator are available within the decorator's scope. The decorated function receives its usual arguments (*args
and**kwargs
).