The role of Python decorators

1. Definitions

In python, you often see the @ symbol, and the following function is a decorator. For example, when defining a class, @property is used to convert a method into an attribute of the class, which is also a decorator. The decorator can be understood as adding a behavior to the function. This behavior is a general behavior that is meaningful to your project code. Common behaviors include printing the day, printing the calculation time of the function, and for example, the parameter check we have to do this time, etc. . Adding a decorator to a function can not only increase the function, but also simplify the code and improve readability.

In addition, python has 3 built-in functions decorator , respectively @staticmethod, @classmethod and @property, where staticmethod(), classmethod() and property() are all built-in functions of Python.

role 1 decorator

Example: Calculate the runtime of a function,

import time


def timer(func):
    """
    Decorator function for timing

    :param func: decorated function
    :return: Closure function, which encapsulates custom behavior and invocation of decorated function
    """

    def wrapper(*args, **kwargs):
        """
        Closure function

        :param args: Positional parameters of the decorated function
        :param kwargs: keyword arguments of the decorated function
        :return: int,The computed result of the decorated function
        """
        t1 = time.time()  # Starting time
        r = func(*args, **kwargs)  # Called by a decorated function
        t2 = time.time()  # End Time
        cost = t2 - t1  # Calculate time
        print('function cost{} second'.format(cost))
        return r

    return wrapper

The function timer we defined is a decorator function (used to wrap, placed at the outermost), this function is special, it can pass in the function as a parameter, here is our decorated function (distinguished from the decorated function) , and then the returned object is also a function, which is called a closure function. In a closure function, two things need to be done, one is to define the behavior (print the function calculation time), and the other is to call the function calculation .

After the decorator function is written, it can be used directly with the @ symbol. We define a function add that needs to be decorated. The function is to add two numbers. When using the decorator, add @timer on the line before the function.

@timer
def add(x, y):
    time.sleep(2)
    return x + y

The @ symbol is the syntactic sugar of the decorator, which can be understood as the shortcut key of the decorator. If the @ symbol is not used, it is also possible to call the decorator function directly, but the syntactic sugar is more concise. The purpose of the @ symbol is to execute the following statement before the function is actually called:

add = timer(add)
print(add(1, 2))
#function cost2.000091075897217 second 3

Calling the decorated function of add will pass the decorated function as a parameter to the decorated function, similar to add(1,2), where the decorated function will be called first, and then func will be executed in the decorated function

Action 2 Parameter check

As the name implies, @ in this part is used for parameter checking, such as

First, define a parameter check decorator to check whether the input parameters are consistent with the definition, otherwise an error will be reported

import collections
import functools
import inspect


def para_check(func):
    """
    The function parameter check decorator needs to be matched with the function annotation expression ( Function Annotations)use
    """
    msg = 'Argument {argument} must be {expected!r},but got {got!r},value {value!r}'

    # Get the parameters of the function definition
    sig = inspect.signature(func)
    parameters = sig.parameters  # parameter ordered dictionary
    arg_keys = tuple(parameters.keys())  # parameter name

  #Inner decorator that displays the original name of the calling function
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        CheckItem = collections.namedtuple('CheckItem', ('anno', 'arg_name', 'value'))
        check_list = []

        # collect args *args The incoming parameters and the corresponding function parameter annotations, namely x: init,
        for i, value in enumerate(args):
            arg_name = arg_keys[i]
            anno = parameters[arg_name].annotation
            check_list.append(CheckItem(anno, arg_name, value))

        # collect kwargs **kwargs incoming parameters and corresponding function parameter annotations
        for arg_name, value in kwargs.items():
            anno = parameters[arg_name].annotation
            check_list.append(CheckItem(anno, arg_name, value))

        # check type
        for item in check_list:
            if not isinstance(item.value, item.anno):
                error = msg.format(expected=item.anno, argument=item.arg_name,
                                   got=type(item.value), value=item.value)
                raise TypeError(error)

        return func(*args, **kwargs)

    return wrapper

Action 3 Annotation Expression

Annotation expression is a feature of python, which makes it possible to define the type of input parameters when the function is defined, which improves the readability of the function. Use: symbol plus parameter type to achieve

def anno(x: str, y: dict, z: int = 123):
    print("x type:{},y type:{},z type{}".format(type(x), type(y), type(z)))


anno('123', {'a': 1}, 2)
#x type:<class 'str'>,y type:<class 'dict'>,z type<class 'int'>

In the parameter type use: to define the parameter type

Tags: Python programming language

Posted by jj33 on Sun, 25 Sep 2022 01:50:51 +0930