Python - decorator functions

Posted by WhiteShade6 on Sat, 15 Feb 2020 15:45:30 +0100

Wedge

As a python developer who can write functions, we are going to work in the company from today. Write a function, and it will be used by other developers.

def func1():
    print('in func1')

At the end of the quarter, the leaders of the company will give performance bonus to everyone. They propose to review the results developed by everyone in this period. What is the standard of the review? Is to count the execution time of each function.

What are you going to do at this time?

When you think about it, it's easy to change the function:

import time
def func1():
    start = time.time()
    print('in func1')
    print(time.time() - start)

func1()

Half a year since I came to the company, I have written 2000 + functions and changed them one by one. A week has passed. After the leader's review, I will delete them one by one... Another week passed... Is this disturbing?

You don't think you can. You can't bother to tell all the developers that now you add a sentence of calculating time to your original code?

import time
def func1():
    print('in func1')

start = time.time()
func1()
print(time.time() - start)

Still can't, because this is too much trouble for the development colleagues.

Then what shall I do? You have an idea. You write a timer function...

import time
def timer(func):
    start = time.time()
    func()
    print(time.time() - start)

def func1():
    print('in func1')


def func2():
    print('in func2')

timer(func1)
timer(func2)

Does that seem a lot easier? No matter how many functions we write, we can call this timing function to calculate the execution time of the function... Although the modification cost has become very small now, but for colleagues, the call mode of this function has been changed. If a colleague uses your method more than 2w times in his code because he believes in you, the boat of your friendship will turn completely after he modifies the code.

All you have to do is let your colleagues still call func1, but they can achieve the effect of calling timer method.

import time
def timer(func):
    start = time.time()
    func()
    print(time.time() - start)

def func1():
    print('in func1')

func1 =timer  #It would be perfect if it could... It is a pity.
func1()

Unfortunately, the above code will report an error, because the timer method needs to pass a func parameter. We can't pass a parameter when assigning a value, because as long as we execute func1 = timer(func1), the timer method will directly execute, and the following sentence func1 has no meaning at all. Here, our thinking seems to be in a stalemate...

The forming process of decorator

import time

def func1():
    print('in func1')

def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

func1 = timer(func1)
func1()

After working so hard for a long time, it has finally begun to take shape! Now it's basically perfect. The only thing that stands in the way is that we need to make an assignment call...

You think it's eyesight, and python developers also think it's eyesight, so we have a sentence of syntax sugar to solve this problem!

import time
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

@timer   #==> func1 = timer(func1)
def func1():
    print('in func1')


func1()

Here, we can briefly summarize:

The essence of decorator: a closure function

Function of decorator: extend the function of the original function without modifying the original function and its calling method

There is a final problem to be solved. The decorators we just discussed are all functions with no parameters. Now what about decorating a function with parameters?

def timer(func):
    def inner(a):
        start = time.time()
        func(a)
        print(time.time() - start)
    return inner

@timer
def func1(a):
    print(a)

func1(1)

It's not hard to decorate a function with parameters, but if you have two functions, the parameters you need to pass are different?

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func1 = timer(func1)
def func1(a,b):
    print('in func1')

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func1('aaaaaa','bbbbbb')
print(func2('aaaaaa'))

Now the argument problem has been solved perfectly, but what if your function has a return value?

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func2('aaaaaa')
print(func2('aaaaaa'))

The decorator just now is perfect, but normally our way to view some information about the function fails here

def index():
    '''This is a home page message'''
    print('from index')

print(index.__doc__)    #How to view function comments
print(index.__name__)   #How to view function names

In order not to let them fail, we need to add a little bit to the decorator to improve it:

from functools import wraps

def deco(func):
    @wraps(func) #Right above the innermost function
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

@deco
def index():
    '''Ha ha ha ha'''
    print('from index')

print(index.__doc__)
print(index.__name__)

Open and closed principle

1. Open to expansion

Why open to expansion?

We say that it is impossible for any program to have all the functions in mind at the beginning of design without any update and modification in the future. So we have to allow code to expand and add new features.

2. The modification is closed

Why are changes closed?

As we just mentioned, because a function we wrote is likely to have been delivered to other users. If we modify it at this time, it is likely to affect other users who are already using the function.

The decorator perfectly follows the principle of opening and closing.

The main functions of the decorator and the fixed structure of the decorator

Main functions of decorators

Add functions before and after the function without changing the function call mode.

Fixed form of decorator

def timer(func):
    def inner(*args,**kwargs):
        '''What to do before executing a function'''
        re = func(*args,**kwargs)
        '''What to do after executing a function'''
        return re
    return inner
from functools import wraps

def deco(func):
    @wraps(func) #Right above the innermost function
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

Decorator with parameters

If you have thousands of functions that use a decorator, and now you want to cancel all these decorators, what do you do?

Cancel one by one? Working day and night for three days...

After two days, your leader has figured it out, and let you add...

def outer(flag):
    def timer(func):
        def inner(*args,**kwargs):
            if flag:
                print('''What to do before executing a function''')
            re = func(*args,**kwargs)
            if flag:
                print('''What to do after executing a function''')
            return re
        return inner
    return timer

@outer(False)
def func():
    print(111)

func()

Multiple decorators decorate the same function

Sometimes, we also use multiple decorators to decorate the same function.

def wrapper1(func):
    def inner():
        print('wrapper1 ,before func')
        func()
        print('wrapper1 ,after func')
    return inner

def wrapper2(func):
    def inner():
        print('wrapper2 ,before func')
        func()
        print('wrapper2 ,after func')
    return inner

@wrapper2
@wrapper1
def f():
    print('in f')

f()

Topics: Python