Python adding parameters to decorators
Python Adding Parameters to Decorators A common requirement is to customize a decorator with additional parameters. We can do more complex processing than simply creating a composite function f o g(x)
. Using parameterized decorators, we can create (f(c) o g)(x)
. We have already used the parameter C as part of creating the wrapper f(c)
. This parameterized composite function f(c) o g
can then be used with the actual data x.
In Python, you can write the following code:
@deco(arg)
def func(x):
something
This involves two steps. First, the arguments are applied to the abstract decorator to create a concrete decorator. Then, this concrete decorator, the parameterized function deco(arg)
, is applied to the base function definition to create the decorated function.
The effect of this decorator function is as follows:
def func(x):
return something(x)
concrete_deco = deco(arg)
func= concrete_deco(func)
It actually does the following three things:
(1) Define a function func()
;
(2) Apply the abstract decorator deco()
to the argument arg
to create a concrete decorator concrete_deco()
;
(3) Apply the concrete decorator concrete_deco()
to the base function to create a decorated version of the function, which is actually deco(arg)(func)
.
Decorators with parameters include indirect construction of the final function. This seems to go beyond simple higher-order functions and into the more abstract realm of higher-order functions that create higher-order functions.
You can extend the bad-data-aware decorator to create a more flexible transformer. Below, we define a @bad_char_remove
decorator that takes the character to be removed as a parameter. This parameterized decorator is as follows:
import decimal
def bad_char_remove(
*char_list: str
) -> Callable[[F], F]:
def cr_decorator(function: F) -> F:
@wraps(function)
def wrap_char_remove(text: str, *args, **kw):
try:
return function(text, *args, **kw)
except (ValueError, decimal.InvalidOperation):
cleaned = clean_list(text, char_list)
return function(cleaned, *args, **kw)
return cast(F, wrap_char_remove)
return cr_decorator
A parameterized decorator has two inner function definitions.
- Abstract decorator: The
cr_decorator
function converts its bound free variablechar_list
into a concrete decorator. This decorator is then returned and applied to a function, resulting in a function wrapped within thewrap_char_remove
function. The type variableF
, acting as a type hint, declares that the wrapping operation preserves the type of the wrapped function. - Decorated wrapper: The
wrap_char_remove
function replaces the original function with the wrapped version. Because of the use of the@wraps
decorator, the name of the wrapped base function replaces the__name__
attribute (among other attributes) of the new function.
The entire decorator, bad_char_remove()
, binds its arguments to the abstract decorator and returns the concrete decorator. The type hint clarifies that the return value is a Callable
object that converts a Callable
function into another Callable
function. The specific decorator is then applied to the subsequent function definition according to the language rules.
The following clean_list()
function removes all characters from a given list.
from typing import Tuple
def clean_list(text: str, char_list: Tuple[str, ...]) -> str:
if char_list:
return clean_list(
text.replace(char_list[0], ""), char_list[1:])
return text
The rule is simple enough that a recursive approach is used, which can also be optimized into a loop.
You can use the @bad_char_remove
decorator to create a conversion function, as shown below:
@bad_char_remove("$", ",")
def currency(text: str, **kw) -> Decimal:
return Decimal(text, **kw)
The decorator above wraps the currency()
function. The essential feature of the currency()
function is a reference to the decimal.Decimal
constructor.
This currency()
function can now handle different data formats.
>>> currency("13")
Decimal('13')
>>> currency("<span class="katex math inline">3.14")
Decimal('3.14')
>>> currency("</span>1,701.00")
Decimal('1701.00')
Next, we can use a relatively simple map(currency, row)
method to process the input data, converting the source data from strings to usable Decimal
values. The error handling try:/except:
statements have been isolated into a function for building composite conversion functions.
A similar design can be used to create functions that can tolerate null values. These functions will use similar try:/except:
wrappers but simply return None
.