[Python] functional programming - decorator

Posted by ashell on Tue, 01 Mar 2022 10:50:09 +0100

catalogue

1, The essence of decorator:

function closure:

2, Use method of decorator:

Function closures that retain function parameters and return values:

3, Execution sequence of multiple decorators:

4, To create a decorator with parameters:

1, The essence of decorator:

The essence of decorator is the syntax sugar of function closure

function closure:

Function closure is a term in functional language (function is a first-class citizen and can be used as a variable). Function closure: a function whose parameters and return values are functions. It is used to enhance function functions and face aspect programming (AOP)

import time


# The console prints odd numbers within 100:
def print_odd():
    for i in range(100):
        if i % 2 == 1:
            print(i)


# Function closure: used to enhance function func: function of adding statistical time to function func:
def count_time_wrapper(func):
    def improved_func():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f"It takes {end_time - start_time} S to find all the odds in range !!!")

    return improved_func


if __name__ == '__main__':
    # Call count_time_wrapper enhancement function
    print_odd = count_time_wrapper(print_odd)
    print_odd()

A closure is essentially a function. The incoming parameters and return values of the closure function are functions. The return value function obtained by the closure function is the result of the enhancement of the incoming function.

Log decorator:

def log_wrapper(func):
    """
    closure,For enhancement function func: to func Add log function
    """

    def improved_func():
        start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))  # Start time
        func()  # Execution function
        end_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))  # End time
        print("Logging: func:{} runs from {} to {}".format(func.__name__, start_time, end_time))

    return improved_func

2, Use method of decorator:

Function enhancement through decorators is just a syntax sugar, which is essentially consistent with the previous program (using function closures).

import time


# Function closure: used to enhance function func: function of adding statistical time to function func:
def count_time_wrapper(func):
    def improved_func():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f"It takes {end_time - start_time} S to find all the odds in range !!!")

    return improved_func


# The console prints odd numbers within 100:
@count_time_wrapper  # Add decorator
def print_odd():
    for i in range(100):
        if i % 2 == 1:
            print(i)


if __name__ == '__main__':
    # Using @ decorator (enhanced function name) to add decorator to the current function is equivalent to executing the following statement:
    # print_odd = count_time_wrapper(print_odd)
    print_odd()

When the decorator calls the decorated function for the first time, it will be enhanced only once. The next call is still to call the enhanced function, and the enhancement will not be repeated!

Function closures that retain function parameters and return values:

  • The previously written function closure does not retain the parameter list and return value of the original main function function when enhancing the main function function.
  • Write a function closure that retains the parameter list and return value:
def general_wrapper(func):
    def improved_func(*args, **kwargs):
        # Enhanced functions:
        ret = func(*args, **kwargs)
        # Enhanced functions:
        return ret

    return improved_func

Optimize decorator (parameter transfer, setting return value):

import time


# Function closure: used to enhance function func: function of adding statistical time to function func:
def count_time_wrapper(func):
    
    # Enhancement function:
    def improved_func(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"It takes {end_time - start_time} S to find all the odds in range !!!")
        
        # Return value of original function
        return result

    return improved_func


# Calculate the sum of 0-lim odd numbers:
@count_time_wrapper
def count_odds(lim):
    cnt = 0
    for i in range(lim):
        if i % 2 == 1:
            cnt = cnt + i

    return cnt


if __name__ == '__main__':
    result = count_odds(10000000)

    print(f"The calculation result is{result}!")

3, Execution sequence of multiple decorators:

# Decorator 1:
def wrapper1(func1):
    print("set func1")  # Output when wrapper1 decorates the function

    def improved_func1(*args, **kwargs):
        print("call func1")  # Output when wrapper 1 decorated function is called
        func1(*args, **kwargs)
        return None

    return improved_func1


# Trimmer 2:
def wrapper2(func2):
    print("set func2")  # Output when wrapper2 decorates the function

    def improved_func2(*args, **kwargs):
        print("call func1")  # Output when wrapper 2 decorated function is called
        func2(*args, **kwargs)
        return None

    return improved_func2


@wrapper1
@wrapper2
def original_func():
    pass


if __name__ == '__main__':
    original_func()

    print("------------")

    original_func()

The execution result obtained here is that the wrapper2 decorator executes first because: the program executes from top to bottom. When running to:

@wrapper1
@wrapper2
def original_func():
    pass

This code is parsed as follows by using function closure:

original_func = wrapper1(wrapper2(original_func))

Therefore, first decorate wrapper2, then decorate the enhancement function decorated by wrapper2, and then decorate wrapper1 to return the final enhancement function.

4, To create a decorator with parameters:

Decorators allow parameters to be passed in. A decorator with parameters will have three-tier functions, as shown below:

import functools


def log_with_param(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('call %s():' % func.__name__)
            print('args = {}'.format(*args))
            print('log_param = {}'.format(text))
            return func(*args, **kwargs)

        return wrapper

    return decorator


@log_with_param("param!!!")
def test_with_param(p):
    print(test_with_param.__name__)


if __name__ == '__main__':
    test_with_param("test")

Remove its @ syntax and restore the form of function call:

# Pass in the parameters of the decorator and receive the returned decorator function
decorator = log_with_param("param!!!")

# Incoming test_with_param function
wrapper = decorator(test_with_param)

# Call decorator function
wrapper("I'm a param")

Topics: Python Back-end