In Python, functions can be passed as arguments to a function, or functions can be copied to variables and called from variables. Decorator can extend the function of a function, make a decorator annotation for the function, you can put the function defined in the decorator in advance of all functions, improve the degree of reuse of code.

Now here’s a scenario.

Clock in

Internet companies have all kinds of employees, programmers, receptionists… Programmers have to clock in before they turn on the computer, receptionists have to clock in before they turn on the computer, receptionists have to clock in before they turn on the computer, receptionists have to clock in before they turn on the computer. In other words, punching in is the first common action of all employees, so it can be taken out as the common logic.

Ordinary function call methods

Naturally, this can be achieved as follows.

def di(f) :
    print('%s clock in, drop... ' % f.__name__)
    return f()


def boot() :
    print('开机')


def open() :
    print('open')


if __name__ == '__main__':
    """ Programmers need to punch in the fingerprint machine outside the door before they turn on the computer and the front desk before they open the door. "" "
    di(boot)
    di(open)

Copy the code

Di (f) prints f.__name__, the function name of f, and returns the result of f().

Note: __name__ is the name of the module itself if it is imported as a module, and __main__ is returned if the module itself is executed as a script.

Execution Result:

Boot punch, drip... Turn on open clock, drop... Open the doorCopy the code

This way, if you have a lot of functions to call, it’s cumbersome, and decorators come in handy.

Simple decorator with @ grammar sugar

Decorators: A way of dynamically adding functionality while code is running, called decorators.

Simple decorator

Define a di(f) method, passing in the function of the logic to be executed as an argument, and define a Wrapper function that returns the result of f. Inside if __name__ == ‘__main__’:, this decorator is called, without modifying the defined function, adding the function “punch” dynamically at run time.

import functools

# Simple decorator
def di(f) :
    """ Programmers need to punch in the fingerprint machine outside the door before they turn on the computer and the front desk before they open the door. :param f: passes a function :return: """
    # Copy the original function's __name__ attributes to wrapper()
    @functools.wraps(f)
    def wrapper() :
        print('%s clock in, drop... ' % f.__name__)
        return f()
    return wrapper


def boot() :
    print('开机')


def open() :
    print('open')


if __name__ == '__main__':

    # First, simple decorators
    a = di(boot)
    a1 = di(open)
    print(a.__name__) Wrapper with @functools.wraps(f) will result in boot
    a()
    a1()
Copy the code

The return value of di(boot) is the Wrapper function. The wrapper function is called by a() and boot returns the value. Same thing with di(Open).

The results of

Boot, boot, punch, drip... Turn on open clock, drop... Open the doorCopy the code

Since di(boot) returns a wrapper function, print(a.__name__) will return a wrapper function. Functools.wraps (f) This annotation will copy the __name__ attributes of the original boot function into wrapper(). This annotation will also work, so print(A. __name__) will result in wrapper.

Second, @ grammar sugar

Decorators can also be applied to functions using @ syntax sugar, recommended.

import functools

def di(f) :
    """ Programmers need to punch in the fingerprint machine outside the door before they turn on the computer and the front desk before they open the door. :param f: passes a function :return: """
    # Copy the original function's __name__ attributes to wrapper()
    @functools.wraps(f)
    def wrapper() :
        print('%s clock in, drop... ' % f.__name__)
        return f()
    return wrapper


# @ Grammar sugar
@di
def boot2() :
    print('开机')


@di
def open2() :
    print('open')
    
    
if __name__ == '__main__':

    # Second, @grammar sugar
    boot2()
    open2()
Copy the code

The @di notation is equivalent to a2 = di(boot2) a2(). You don’t have to bother, because with the @ sign, you can just call the decorator with boot2().

The results of

Boot2 punch in, drip... Start open2 and punch, drop... Open the doorCopy the code

Business logic functions require parameters

Business logic functions may require arguments, such as:

def boot(name) :
    print('% s' boot % name)
Copy the code

So, just change the previous decorator to:

import functools

Business logic functions need arguments
def di(f) :
    """ Programmers need to punch in the fingerprint machine outside the door before they turn on the computer and the front desk before they open the door. :param f: passes a function :return: """
    # Copy the original function's __name__ attributes to wrapper()
    @functools.wraps(f)
    def wrapper(*args, **kwargs) :
        print('%s clock in, drop... ' % f.__name__)
        return f(*args, **kwargs)
    return wrapper


@di
def boot(name) :
    print('% s' boot % name)


if __name__ == '__main__':
    boot('keguang')
Copy the code

Results:

Boot punch, drip... Keguang bootCopy the code

Add *args, **kwargs to the wrapper and call f(*args, **kwargs) in the boot. By the way:

  • *args: An array argument can be passed
  • **kwargs: You can pass in onek-vThe parameter

The sequence corresponds, with the array parameters first. For example:

def f(*args, **kwargs) :
    print('args=', args)
    print('kwargs=', kwargs)

print(f(1.2.3, a = 'a', b = 'b'))

# the results
# args= (1, 2, 3)
# kwargs= {'a': 'a', 'b': 'b'}
Copy the code

Decorator with parameters

If the decorator also takes arguments, for example if an employee comes to work early in the morning < 9:00, we can praise it, then it is equivalent to just putting a function di_args around the di() in front of it, inside the Wrapper. Using this parameter

import functools

# Decorator with parameters
def di_args(time) :
    def di(f) :
        """ Programmers need to punch in the fingerprint machine outside the door before they turn on the computer and the front desk before they open the door. :param f: passes a function :return: """
        # Copy the original function's __name__ attributes to wrapper()
        @functools.wraps(f)
        def wrapper(*args, **kwargs) :
            if time < '9':
                print('Early, great... ')

            print('%s clock in, drop... ' % f.__name__)
            return f(*args, **kwargs)
        return wrapper
    return di


@di_args('8')
def boot(name) :
    print('% s' boot % name)


if __name__ == '__main__':
    boot('keguang')
Copy the code

The argument is passed in @di_args(‘8:00’), sort of like a Java annotation. Finally, call boot(‘keguang’) and result:

It's early, it's great... Boot punch, drip... Keguang bootCopy the code

Class decorator

Class decorators rely primarily on the class’s __call__ method, which is called when a decorator is attached to a function using the @ form.

Class decorator
class di(object) :
    def __init__(self, f) :
        self._f = f

    def __call__(self, *args, **kwargs) :
        print('decorator start... ')
        self._f()
        print('decorator end... ')


@di
def boot() :
    print('开机')


if __name__ == '__main__':
    boot()
Copy the code

With the @di decorator identifier, boot is used to instantiate the di class, and then the __call__ function is executed. Object means that the class can pass in any type of argument. The results

decorator start... Boot the decorator end...Copy the code

A typical use of decorators is for logging. If all logic needs to log the health of the program, you can add the log module decorator to that logic.