python advanced (13) decorator

Decorator

The decorator is placed at the beginning of the definition of a function, and it is worn on the head of the function like a hat. Bound to this function. When we call this function, the first thing is not to execute this function, but to pass this function as a parameter into the hat on its head, which we call a decorator.
 

Function of decorator

  1. Import log
  2. Function execution time statistics
  3. Preparation before function execution
  4. Clean up function after function execution
  5. Permission verification and other scenarios
  6. cache
     

Hello, decorator

The use method of the decorator is very fixed

  • First define a decorator (HAT)
  • Then define your business function or class
  • Finally, buckle the decorator (HAT) on the function head
def decorator(func):
    def wrapper(*args, **kw):
        return func()
    return wrapper

@decorator  # You can also add function = decorator(function) at the bottom without decorator, and the effect is the same
def function():
    print("hello, decorator")

In fact, decorators are not necessary for coding, which means that you can do it without decorators
 

Advantages of decorator

  • More elegant and clearer code structure
  • Encapsulate the specific function code into a decorator to improve the code reuse rate and enhance the code readability

Next, I will explain with examples how to write various simple and complex decorators.
 

Log printer

The first is the log printer. Functions realized:

  • Before the function is executed, print a line of log to inform the owner that I want to execute the function.
  • After the function is executed, I can't leave without patting my ass. I'm polite code. Print another line of log and tell the owner that I'm finished.
# This is the decorator function, and the parameter func is the decorated function
def logger(func):
    def wrapper(*args, **kwargs):
        print('Master, I'm ready to start:{} Function:'.format(func.__name__))
        # The real implementation is this line.
        func(*args, **kwargs)
        print('Master, I'm done.')
    return wrapper


@logger  # Equivalent to add = logger(add)
def add(x, y):
    print("{} + {} = {}".format(x, y, x + y))


add(200, 50)

>>> Master, I'm ready to start: add Function:
>>> 200 + 50 = 250
>>> Master, I'm done.

Code parsing

The python interpreter executes from top to bottom. It first defines a logger function that returns a reference to the wrapper function. When @ logger is executed, a closure has been generated internally. In fact, this sentence is equivalent to add = logger(add). The add variable points to the logger function, and the logger function returns the wrapper. Therefore, the add variable actually points to the def wrapper function. When add(200, 50) is executed, If there is no @ logger decorator, normally the print statement under the add function is executed, but now the add variable has pointed to the wrapper function, so at this time, the contents of the wrapper function are executed.
 
So the first sentence output is print('master, I'm ready to start executing '), and then execute func function. At this time, func points to add function. Why? Because add = logger(add) passes in the variable add, def logger(func) becomes def logger(add). Naturally, func(*args, **kwargs) becomes add(*args, **kwargs), which calls the add function
 
So the second sentence is the sum of x and y, and the third sentence is the master. I'm finished
Conclusion: @ logger can be replaced by add = logger(add). Using @ logger is more convenient and clear
 

Time decorator

Implementation function: as the name suggests, it is to calculate the execution time of a function.

# Defines a decorator that calculates the duration of a function
def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        cost = end_time - start_time
        print("Spent{}s".format(cost))
    return wrapper


# Define a download image and save it locally
def downloadPicture(url):
    r = requests.get(url)
    data = r.content
    with open((str(random.random()) + '.jpg'), 'wb') as f:
        f.write(data)

# Use multithreading to download 4 pictures
@timer
def time1():
    t1 = threading.Thread(target=downloadPicture, args=('https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2106474246,1283617636&fm=26&gp=0.jpg', ))
    t1.start()
    t2 = threading.Thread(target=downloadPicture, args=('https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3083490177,4087830236&fm=26&gp=0.jpg', ))
    t2.start()
    t3 = threading.Thread(target=downloadPicture, args=('https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1828982182,1114677948&fm=26&gp=0.jpg', ))
    t3.start()
    t4 = threading.Thread(target=downloadPicture, args=('https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=45058783,2028528740&fm=11&gp=0.jpg', ))
    t4.start()

# Call time1 function
time1()

>>> Spent 0.0011370182037353516s

 

Decorator with parameters

Through the above two simple introductory examples, you should be able to understand the working principle of the decorator.
 
However, the use of ornaments is far more than that. If we go deep into it, there are still many articles. Let's learn this knowledge today.
 
Looking back at the above example, the decorator cannot receive parameters. Its usage can only be applied to some simple scenes. A decorator that does not pass parameters can only execute fixed logic on the decorated function.
 
The decorator itself is a function. As a function, if you can't pass parameters, the function of this function will be very limited and can only execute fixed logic. This means that if the execution of the decorator's logic code needs to be adjusted according to different scenarios, if we can't pass parameters, we have to write two decorators, which is obviously unreasonable.
 
For example, if we want to implement a task that can send email regularly (one email per minute) and a task that can synchronize time regularly (once a day), we can implement a period by ourselves_ Task decorator, which can receive the parameters of a time interval and how often to execute the task.
 
Let's create a pseudo scene by ourselves. We can pass in a parameter in the decorator to indicate the nationality, and say hello in our native language before the function is executed.

def say_hello(country):
    def wrapper(func):
        def deco(*args, **kwargs):
            if country == "china":
                print("Hello!")
            elif country == "america":
                print('hello.')
            else:
                return
            # Where the function is actually executed
            func(*args, **kwargs)
        return deco
    return wrapper


@say_hello("china")
def a():
    pass


@say_hello("america")
def b():
    pass


a()
b()


>>> Hello!
>>> hello.

 

Class decorator without parameters

The above are decorators based on function implementation. When reading other people's code, you can often find decorators based on class implementation.
 
Based on the implementation of class decorator, it must be implemented__ call__ And__ init__ Two built-in functions.

  • init: receive decorated function
  • call: implement decoration logic.

Let's take the simple example of log printing as an example

class Logger(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[INFO]: the function {}() is running...".format(self.func.__name__))
        return self.func(*args, **kwargs)


@Logger
def say(something):
    print("say {}!".format(something))


say("hello")

>>> [INFO]: the function say() is running...
>>> say hello!

 

Class decorator with parameters

In the above example without parameters, you can only print INFO level logs. Under normal circumstances, we also need to print DEBUG WARNING and other levels of logs. This requires you to pass in parameters to the class decorator and specify the level of this function.
 
Class decorators with and without parameters are very different.

  • init: instead of receiving decorated functions, it receives incoming parameters
  • call: receive the decorated function and realize the decoration logic
class Logger(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func):  # Acceptance function
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running...".format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  # Return function


@Logger(level='WARNING')
def say(something):
    print("say {}!".format(something))


say("hello")

>>> [WARNING]: the function say() is running...
>>> say hello!

Reference link: https://mp.weixin.qq.com/s/-jy7v4tt9fmMpMcfPKWNfQ

Posted by man12_patil3 on Thu, 14 Apr 2022 22:18:00 +0930