Creating Decorators with Arguments
So far, we've been using the @logged decorator, which always prints a fixed message. But what if you want to customize the message format? In this section, we'll learn how to create a new decorator that can accept arguments, giving you more flexibility in how you use decorators.
Understanding Parameterized Decorators
A parameterized decorator is a special type of function. Instead of directly modifying another function, it returns a decorator. The general structure of a parameterized decorator looks like this:
def decorator_with_args(arg1, arg2, ...):
def actual_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
## Use arg1, arg2, ... here
## Call the original function
return func(*args, **kwargs)
return wrapper
return actual_decorator
When you use @decorator_with_args(value1, value2) in your code, Python first calls decorator_with_args(value1, value2). This call returns the actual decorator, which is then applied to the function that follows the @ syntax. This two - step process is key to how parameterized decorators work.
Let's create a @logformat(fmt) decorator that takes a format string as an argument. This will allow us to customize the logging message.
- Open
logcall.py in the WebIDE and add the new decorator. The code below shows how to define both the existing logged decorator and the new logformat decorator:
from functools import wraps
def logged(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
def logformat(fmt):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(fmt.format(func=func))
return func(*args, **kwargs)
return wrapper
return decorator
In the logformat decorator, the outer function logformat takes a format string fmt as an argument. It then returns the decorator function, which is the actual decorator that modifies the target function.
- Now, let's test our new decorator by modifying
sample.py. The following code shows how to use both the logged and logformat decorators on different functions:
from logcall import logged, logformat
@logged
def add(x, y):
"Adds two numbers"
return x + y
@logged
def sub(x, y):
"Subtracts y from x"
return x - y
@logformat('{func.__code__.co_filename}:{func.__name__}')
def mul(x, y):
"Multiplies two numbers"
return x * y
Here, the add and sub functions use the logged decorator, while the mul function uses the logformat decorator with a custom format string.
- Run the updated
sample.py to see the results. Open your terminal and run the following command:
cd ~/project
python3 -c "import sample; print(sample.add(2, 3)); print(sample.mul(2, 3))"
You should see output similar to:
Calling add
5
sample.py:mul
6
This output shows that the logged decorator prints the function name as expected, and the logformat decorator uses the custom format string to print the file name and function name.
Now that we have a more flexible logformat decorator, we can redefine our original logged decorator using it. This will help us reuse code and maintain a consistent logging format.
- Update
logcall.py with the following code:
from functools import wraps
def logformat(fmt):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(fmt.format(func=func))
return func(*args, **kwargs)
return wrapper
return decorator
## Define logged using logformat
logged = lambda func: logformat("Calling {func.__name__}")(func)
Here, we use a lambda function to define the logged decorator in terms of the logformat decorator. The lambda function takes a function func and applies the logformat decorator with a specific format string.
- Test that the redefined
logged decorator still works. Open your terminal and run the following command:
cd ~/project
python3 -c "from logcall import logged; @logged
def greet(name):
return f'Hello, {name}'
print(greet('World'))"
You should see:
Calling greet
Hello, World
This shows that the redefined logged decorator works as expected, and we've successfully reused the logformat decorator to achieve a consistent logging format.