Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This paper also participates inProject DigginTo win the creative gift package and challenge the creative incentive money

A decorator

Decorators are an important part of Python. They are often used in Python development. Simply put: it is a function that modifies the functionality of other functions. Helps keep our code shorter and Pythonic.

A Python decorator is essentially a function that allows other functions to add additional functionality without making any code changes, and the return value of the decorator is also a function (a reference to a function).

General understanding of decoration:

Essence: is a closure function

Parameter: function to decorate (function name not function call, i.e. no parentheses)

Return: decorated function (also function name, not function call)

Function: Adds additional functionality to an existing function without making any internal changes

Features: No changes are required to the internal code of the function to be decorated

1. Start with an example

A company has an employee information management system, mainly add employee information, update employee information, delete employee information, view employee information and other functions, anyone can access the operation. After a period of time, it is found that this cannot be done. It should be restricted to perform login verification before each operation, and only those who have logged in can operate. The original functions are as follows:

def add_staff() :
    print('Add Employee Information')

def del_staff() :
    print("Delete employee information")

def upd_staff() :
    print("Update employee information")

def view_staff() :
    print("View employee information")
Copy the code

So the technical director gave this task to both small A and small B2 individual.

Xiao A is an intern who is about to graduate. His implementation method is to add verification logic to each function function, as follows:

def add_staff() :
    # login authentication
    print('Add Employee Information')

def del_staff() :
    # login authentication
    print("Delete employee information")

def upd_staff() :
    # login authentication
    print("Update employee information")

def view_staff() :
    # login authentication
    print("View employee information")
Copy the code

B is an experienced engineer. His solution is to define an additional login verification function and call it in the function function, as follows:

def add_staff() :
    check_login()
    print('Add Employee Information')

def del_staff() :
    check_login()
    print("Delete employee information")

def upd_staff() :
    check_login()
    print("Update employee information")

def view_staff(a)check_login() :
    print("View employee information")

def check_login() :
    # login authentication
Copy the code

After the completion of the task, the technical director respectively looked at the completion of the two people, but did not express any opinion, but also put forward a new requirement, the requirement of a permission check, verification can be operated after passing. So little A and little B go back and modify, little A still adds permission validation logic to each function, little B still defines A permission validation function and calls it in each function separately.

Obviously, the implementation of small B is obviously better than small A, imagine if there are dozens of such methods, according to the operation mode of small A is not the same code to copy dozens of times, once the logic changes and modify dozens of times, maintenance is very troublesome. And small B is much better, just need to change the check function, and then call in the function function on the line, efficiency greatly improved.

However, after the second round of modification, the supervisor still did not give any opinions, and continued to put forward a demand (demand changes, as programmers can understand), requiring that in line with the principle of open and closed, the internal code of the function function that has been implemented is not allowed to be modified, but the function of the function should be extended. That is:

  • Closed: The function code that has been implemented is prohibited from modification
  • Open: Add additional functionality without changing the internal code

This small A is completely confused, do not know where to start, and small B is also A face meng force, so small A small B began to search all kinds of information on the Internet, finally after several times of hard query small B found the answer – decorator

The final modified code is as follows:

def check_login(fun) :
    def inner() :        
        # login authentication
        fun()
    return inner

@check_login
def add_staff() :
    print('Add Employee Information')

@check_login
def del_staff() :
    print("Delete employee information")

@check_login
def upd_staff() :
    print("Update employee information")

@check_login
def view_staff(a)print("View employee information")
Copy the code

In the case of not changing the internal logic at the same time completed the verification function before the operation, the director looked satisfied nodded, small B salary is expected to…

3. Decoration details

Take add_staff as an example alone: The Python interpreter interprets the code from top to bottom as follows:

1) Define a closure function check_login, i.e. load the function check_login into memory

2) @ check_login

3) Define add_staff function

On the surface, the interpreter only interprets these lines of code, because the function’s internal code does not execute until it is called. The @ function name is a syntactic sugar in Python.

The following operations are performed internally at @check_login:

Execute the check_login function

Call the check_login function and pass the function below @check_login as check_login parameter, that is, @check_login is equivalent to check_login(add_staff), so it will be executed internally

def inner() # Login authenticationfun(#)funIs the parameter that was passed infunIs equivalent toadd_staff
return inner 
Copy the code

#fun is the parameter passed in, and fun is equivalent to add_staff

Inner is also a function, but instead of returning the call to the function, it returns the reference (or address of the function) to the add_staff function, which is actually inserted into another function, and called from that function.

So when you want to add employees in the future, the same logic will be invoked, except that every time you add an employee, there will be a login verification. This enables both validation and the original staff addition functionality, which means that the necessary extension functionality is implemented without changing the original logic.

4. Decoration sequence of decorators

Let me show you an example

Define a function that completes wrapping data
def makeBold(fn) :
    def wrapped() :
        return '<b>' + fn() + '</b>'
    return wrapped

# Define function to complete data wrapping
def makeItalic(fn) :
    def wrapped() :
        return '<i>' + fn() + '</i>'
    return wrapped

@makeBold
def test1() :
    return 'hello world-1'

@makeItalic
def test2() :
    return 'hello world-2'

@makeBold
@makeItalic
def test3() :
    return 'hello world-3'

# call function
print(test1())
print(test2())
print(test3())

# run result:

<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>
Copy the code

As you can see from the above example, test1 and test2 are not different from the above. They are both extensions before calling the function function. In this case, the string is bolded or italicized before returning it.

Now let’s focus on the third example, where we decorated test3 twice: @makebold and @Makeitalic. From the order of execution, we can see that we decorated test3 first with makeBold and then with makeItalic. The result is < I >< b > Hello world-3</ b ></ I >. However, the result is the opposite of what we expected. In fact, makeItalic decoration is performed first and makeBold decoration is performed later. That is the focus of this section.

That is, when multiple decorators decorate the same function at the same time, the Python interpreter interprets the function from top to bottom, but decorates it from bottom to top, starting with the decorator closest to the function, which is why we expect the result to be different from the actual result.

With that in mind, let’s go back to the previous example, where the Python interpreter interprets @makebold first and then interprets @makeitalic, and then decorates makeItalic first and then makeBold with the result of the decoration, So we see < b>< I >hello world-3</ I ></ b> instead of < I >< b> Hello world-3</ b></ I >

Here is a code step by step to understand:

Define a function that completes wrapping data
def makeBold(fn) :
    def wrapped() :
        return '<b>' + fn() + '</b>'
    return wrapped

# Define function to complete data wrapping
def makeItalic(fn) :
    def wrapped() :
        return '<i>' + fn() + '</i>'
    return wrapped


@makeBold
@makeItalic
def test3() :
    return 'hello world-3'

Make a makeItalic decoration on test3
@makeItalic
def test3() :
    return 'hello world-3'

# The result after decoration can be understood as
def test3() :
    return '<i>hello world-3</i>'
If there was only one decorator, this would be the end of this step, but now there are multiple decorators decorated at the same time, so there is a makeBold above the makeItalic decoration, then the next step is:
@makeBold
def test3() :
    return '<i>hello world-3</i>'

< I > Hello world-3
       '
Copy the code

5. Decorated functions have arguments and return values

Sometimes we need to decorate a function with parameters and return values. In this case, it is easy to define the decorator by declaring inside the closure that the function to be decorated has the same parameters as the function to be decorated. If there is a return value, the function to be decorated is returned.

Here’s an example:

def outer(func) :
    def inner(a,b) :The arguments declared here are the same as the arguments of the decorated function
        # processing logic
        return func()If the decorated function does not return, you do not need to return it
    reutrn inner This returns a reference to the function

@outer
def test(a,b) :
    return a+b
Copy the code

6. The function and summary of decoration

  1. The introduction of the log

  2. Function execution time statistics

  3. Preprocessing before performing a function

  4. Clean up after the function

  5. For example, permission verification

  6. Caching mechanisms

Summary: In general, to make a decorator more generic, you can define a decorator with an argument of variable length and a return value.