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 variable char_list into a concrete decorator. This decorator is then returned and applied to a function, resulting in a function wrapped within the wrap_char_remove function. The type variable F, 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.

Leave a Reply

Your email address will not be published. Required fields are marked *