This article is participating in Python Theme Month. See the link for details

Analysis of decorative ware

In general, there are three ways to add new functionality to an object:

  • Add methods directly to the class to which the object belongs;
  • Use combination; (Create objects of the existing class in the new class, reuse the functionality of the existing class)
  • Use inheritance; (Can use the existing class, no need to repeatedly write the original class for functional extension)

In general, composition is preferred over inheritance. But decorators fall into the fourth category, changing objects dynamically to extend their functionality.

General decorator application scenarios include log printing, performance testing, transaction processing, permission verification;

How Python’s built-in decorator works

To understand how Python decorators work, you first need to understand the concept of closures. A closure is when a function is nested within a function, and the nested function calls the external function

A variable, an external function returns an embedded function, such a structure is a closure.

Decorators are one application of closures, but the decorator arguments pass functions.

A simple closure example:

def add_num(x) :
    def sum_num(y) :
        return x+y
    return sum_num

add_num5 = add_num(5)
total_num = add_num5(100)
print(total_num)
Copy the code

Comments:

  • The add_num function takes x and returns sum_num, which takes y and adds it to x. The add_num5 object defines the add_num function to accept x as 5, and add_num5(100) returns 105.

Basic use of decorators

Example of a simple calculation function runtime decorator:

def times_use(func) :
    def count_times(*args, **kwargs) :
        start = time.time()
        result  = func(*args, **kwargs)
        end = time.time()
        print(end-start)
        return result
    return count_times

@times_use
def test_decorator() :
    time.sleep(2)
    print("Test Decorator")
test_decorator()
Copy the code

Comments:

  • Test_decorator is passed in as an argument to the times_use function, and count_times retains the original test_decorator code logic. Then, the time is compared with the time after execution to obtain the corresponding time.
  • The advantage of using decorators is that you can modify functions to add new functionality without modifying them or adding new encapsulation.

Example of printing a log decorator by log level (decorator with arguments) :

def use_logging(level) :
    def decorator(func) :
        def wrapper(*args, **kwargs) :
            if level == "warn":
                logging.warn("%s is running"% func.__name__)
            result = func(*args, **kwargs)
            print(result) 
            return result
        return wrapper
    return decorator

@use_logging("warn")
def test_decorator() :
    print("Test Decorator")
    return "Success"
test_decorator()
Copy the code

Example of a class decorator that calculates the runtime of a function:

class logTime:
    def __init__(self, use_log=False):
        self._use_log = use_log

    def __call__(self, func):

        def _log(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            print(result)
            end_time = time.time()
            if self._use_log:
                print(end_time-start_time)
            return result
        return _log

    
@logTime(True)
def test_decorator():
    time.sleep(2)
    print("Test Decorator")
    return "Success"
Copy the code

Functools Wraps use scenarios

Decorators extend functionality while preserving the original code logic, but meta information in the original function is lost, such as __name__, __doc__, and argument lists. In this case

Use functools.wraps, which is also a decorator, but copies the meta information of the original function into the decorator function.

Specific use method:

from functools import wraps

def use_logging(level) :
    def decorator(func) :
        @wraps(func)
        def wrapper(*args, **kwargs) :

            if level == "warn":
                logging.warn("%s is running"% func.__name__)
            result = func(*args, **kwargs)
            print(result) 
            return result
        return wrapper
    return decorator

@use_logging("warn")
def test_decorator() :
    """" Test Decorator DocString"""" time.sleep(2) print("Test Decorator") return "Success" print(test_decorator.__name__) print(test_decorator.__doc__)Copy the code

Comments:

  • Wraps copy the meta information from the incoming test_decorator into the Wrapper decorator function, making the wrapper have the same value as test_decorator

Meta information.

About the execution order of decorators

Multiple decorators are often used in everyday business scenarios such as permission authentication, login authentication, logging, performance checking, and so on. So you’re using multiple decorators

, will involve the issue of decorator execution order. Conclusion: decorator execution order can be divided into two stages :(decorator function) definition stage, (decorator function) execution stage.

  • In the function definition stage, the execution sequence starts from the decorator closest to the function and works from inside out;
  • Function execution stage, execution sequence from outside to inside, layer upon layer of execution;

Multi-decorator example:

def decorator_a(func) :
    print("Get in Decorator_a")

    def inner_a(*args, **kwargs) :
        print("Get in Inner_a")
        result = func(*args, **kwargs)
        return result
    return inner_a

def decorator_b(func) :
    print("Get in Decorator_b")

    def inner_b(*args, **kwargs) :
        print("Get in Inner_b")
        result = func(*args, **kwargs)
        return result
    return inner_b

@decorator_b    
@decorator_a
def test_decorator() :
    """test decorator DocString"""
    print("Test Decorator")
    return "Success"
Copy the code

Running results:

Get in Decorator_a
Get in Decorator_b
Get in Inner_b
Get in Inner_a
Test Decorator
Copy the code

Code comments:

  • The above function can be equivalent to decorator_B (decorator_A (test_decorator()), that is, test_dcorator is passed in as an argument to decorator_A, and then prints “Get in decorator_A “, The inner_A function is given to the upper-layer decorator_B function, which takes inner_A as an argument, prints “Get in decorator_B “, and returns inner_B.
  • Inner_b prints “Get in inner_B “and then calls inner_A. Inner_a prints “Get in Decorator_a” and finally calls the test_decorator function. At its outermost level, this looks like a direct call to the test_decorator function, but you can extend the functionality just described.

Refer to the link

www.zhihu.com/question/26… Segmentfault.com/a/119000000… Blog.csdn.net/u013411246/…