Decorator Operator

Python developers have adeptly resolved the inherent constraint of the number of function parameters in decorators by utilizing internal functions. Decorator operators are no exception to this accomplishment.

from dataclasses import dataclass

# Traditional decorator example

@dataclass
class MyDataclass:
    data: int

@dataclass(unsafe_hash=True, slots=True, kw_only=True)
class MyDataclass:
    data: int

# Decorator operator example

requests.get @= retry(3)
res = requests.get(unstable_source)

# Alternatively, the 'Similarity with Natural Language' section below provides a relevant example.

Similarity with Natural Language

Interestingly, decorator operators enable functions to be executed in a manner that closely resembles natural language. English follows a subject+verb[+object][+modifier](S+V[+O][+M]) structure, and while Python’s function structure is similar, it’s not identical. The verb, object, and modifier are positioned appropriately, but the subject can only follow the function[1].

Decorator operators may assume a form more akin to natural language than functions, as shown below:

subject @verb(object, modifier=...)

For instance, consider the following examples:

sorted @= smart_partial

my_iterable = (random.random() for _ in 10 @range)

# my_iterable is sorted with `lambda x: abs(x - 0.5)` key.
my_iterable @= sorted(key=lambda x: abs(x - 0.5))

print(my_iterable)
# output(example): [0.5539782183769761, 0.3935715075178017, 0.6101377041323496, 0.631808516311081, 0.34539457243787297, 0.32361153092850614, 0.27686779149177143, 0.8051689788456509, 0.943614571438918, 0.019109995811058544]

The structure of the smart_partial used in this code is as follows:

def smart_partial(f: Callable, *, skip_trying: bool = False):
    def wrapper(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except TypeError:
            # Parameter was incomplete
            def inner(*inner_args, **inner_kwargs):
                return f(*inner_args, *args, **(kwargs | inner_kwargs))
            return inner
    return wrapper

smart_partial @= smart_partial

Most well-crafted functions employ a format that places the most critical and essential arguments at the forefront, with secondary and optional arguments trailing. Consequently, many existing functions can use smart_partial to enable them to have the SV[O][M] format.

Of course, this is not a rigid rule that all functions, including decorators, must strictly adhere to.

The decorator operator shares remarkable similarities with the traditional decorator in terms of functionality, purpose, and application, despite its placement not preceding the value or function. Therefore, it can be regarded as a variant or alternative form of the decorator.

The decorator operator, similar to the traditional decorator, has the ability to prevent the redundancy of the value’s (in the case of the traditional decorator, the function) name, but its functionality extends beyond this.

You can consider the following case as an example:

quite_long_variable_name = my_generator(123)
quite_long_variable_name = list(quite_long_variable_name)


# The above code can be abbreviated as follows:
quite_long_variable_name = my_generator(123)
quite_long_variable_name @= list

  1. While classes can be employed, there are circumstances where this is not feasible. ↩︎