How to use the decorator in python

  • 2021-11-10 10:20:35
  • OfStack

Directory 1. How do requirements come from
2. To respond to all changes with the same, it is also a change
3. Minimize changes
4. Use decorators for functions with arguments
Step 5 Give decorator parameters
6. Decorator with class parameters
7. Apply more than one decorator to a function
8. As a class

1. How does the demand come from

The definition of decorator is very abstract. Let's look at a small example.


def foo():
    print('in foo()')
foo()

This is a boring function. That's right. But suddenly one of the more boring people, we call him B Jun, said I want to see how long it takes to execute this function, OK, then we can do this:


import time

def foo():
    start = time.time()
    print('in foo()')
    time.sleep(2)
    end = time.time()
    print(f'used:{end - start}')

foo()

Good, the function looks impeccable. However, B, who is in pain, suddenly doesn't want to see this function at the moment, and he is more interested in another function called foo2.

What shall I do? If you copy the above newly added code to foo2, it will be a big taboo ~ isn't it the most annoying to copy something! Moreover, what if B continued to look at other functions?

2. To respond to all changes with the same, it is also a change

Remember, the function is a first-class citizen in Python, so we can consider redefining a function timeit, passing the reference of foo to him, and then calling foo in timeit and timing it, so that we can achieve the purpose of not changing the definition of foo, and no matter how many functions B looks at, we don't have to modify the definition of the function!


import time

def foo():
    print('in foo()')

def timeit(func):
    start = time.time()
    func()
    time.sleep(2)
    end = time.time()
    print('used:', end - start)

timeit(foo)

It seems that there is no logical problem, 1 cut is beautiful and works normally! ..... Wait, we seem to have modified the code of the calling part. Originally, we called it this way: foo (), but after modification, it became: timeit (foo). In this case, if foo is called at N, you will have to modify the code at N. Or more extreme, consider the situation where the code called somewhere can't be modified, for example, this function is given to others for use.

3. Minimize changes

In this case, let's think of a way not to modify the calling code; If the calling code is not modified, it means that calling foo () needs to have the effect of calling timeit (foo). We can think of assigning timeit to foo, but timeit seems to take one parameter... find a way to unify the parameters to one! If timeit (foo) does not directly produce the calling effect, but returns a function corresponding to foo parameter list 1... it is very easy to handle. Assign the return value of timeit (foo) to foo, and then the code for calling foo () does not need to be modified at all!


# -*- coding: UTF-8 -*-
import time

def foo():
    print('in foo()')

#  Definition 1 Timer, passed in 1 And return another 1 Methods with additional timing function 
def timeit(func):
    #  Definition 1 Embedded wrapper function, which wraps the incoming function with timing function 
    def wrapper():
        start = time.time()
        func()
        time.sleep(2)
        end = time.time()
        print('used:', end - start)

    #  Returns the wrapped function 
    return wrapper

foo = timeit(foo)
foo()

In this way, a simple timer is ready! We only need to add foo = timeit (foo) after defining foo and before calling foo to achieve the purpose of timing, which is the concept of decorator, which looks like foo is decorated by timeit. In this example, the function is timed in and out, which is called a cross-section (Aspect), and this programming method is called section-oriented programming (Aspect-Oriented Programming). Compared with the top-down execution mode used by traditional programming, it is like inserting a piece of logic horizontally in the flow of function execution. In a specific business area, a large amount of duplicate code can be reduced. There are quite a few terms for aspect-oriented programming, so we won't introduce them here. If you are interested, you can look for relevant information.

This example is for demonstration only and does not take into account the foo with parameters and return values. It is up to you to perfect it:)

It seems that the above code can no longer be simplified, so Python provides a syntax sugar to reduce the input of characters.


import time


def timeit(func):
    def wrapper():
        start = time.time()
        func()
        time.sleep(2)
        end = time.time()
        print('used:', end - start)

    return wrapper


@timeit
def foo():
    print('in foo()')


foo()

Focus on @ timeit in line 11. Adding this line to the definition is exactly equivalent to writing foo = timeit (foo). Never think @ has other magic. In addition to 1 less character input, there is one additional benefit: it looks more decorator-like.

As you can see here, the decorator in python is essentially a function, which takes other functions as parameters and replaces them with a brand-new modified function.

4. Use decorators for functions with arguments

If the function to be wrapped has parameters, it is not troublesome. As long as the parameters and return values of the embedded wrapper function are the same as those of the original function, the decorative function can return the embedded wrapper function object


import datetime,time

def out(func):
    def inner(*args):
        start = datetime.datetime.now()
        func(*args)
        end = datetime.datetime.now()
        print(end-start)
        print("out and inner")
    return inner

@out
def myfunc(*args):
    time.sleep(1)
    print("args is{}".format(args))

myfunc("lalalal")

Step 5 Give decorator parameters

It is not difficult to pass parameters to the decorator. Compared with the previous example, there is only one layer of packaging on the outer layer


#coding:utf-8
def outermost(*args):
	def out(func):
		print (" Decorator parameters {}".format(args))
		def inner(*args):
			print("innet start")
			func(*args)
			print ("inner end")
		return inner
	return out

@outermost(666)
def myfun(*args):
	print (" Try the case where both decorators and functions take parameters , Decorated function parameters {}".format(args))

myfun("zhangkun")

6. Decorator with class parameters

It doesn't matter what type the parameter is. You see, the parameter is a class.


class locker:
    def __init__(self):
        print("locker.__init__() should be not called")

    @staticmethod
    def acquire():
        print("locker.acquire() static method be called")

    @staticmethod
    def release():
        print("locker.release() static method be called")

def outermost(cls):
    def out(func):
        def inner():
            cls.acquire()
            func()
            cls.release()
        return inner
    return out

@outermost(locker)
def myfunc():
    print("myfunc called")

myfunc()

7. Apply more than one decorator to a function

1 function can have multiple decorators, but pay attention to the order


class mylocker:
    def __init__(self):
        print("mylocker.__init__() called.")

    @staticmethod
    def acquire():
        print("mylocker.acquire() called.")

    @staticmethod
    def unlock():
        print("  mylocker.unlock() called.")

class lockerex(mylocker):
    @staticmethod
    def acquire():
        print("lockerex.acquire() called.")

    @staticmethod
    def unlock():
        print("  lockerex.unlock() called.")

def lockhelper(cls):
    def _deco(func):
        def __deco2(*args, **kwargs):
            print("before %s called." % func.__name__)
            cls.acquire()
            try:
                return func(*args, **kwargs)
            finally:
                cls.unlock()
        return __deco2
    return _deco

class example:
    @lockhelper(mylocker)
    @lockhelper(lockerex)
    def myfunc2(self, a, b):
        print(" myfunc2() called.")
        print(a+b)

a = example()
a.myfunc2(1,2)

8. As a class

Although decorators can almost always be implemented as functions, in some cases, it may be better to use user-defined classes


import time


class DerocatorAsClass:
    def __init__(self,funcation):
        self.funcation = funcation

    def __call__(self, *args, **kwargs):
        #  Before calling the function , Do something 
        result = self.funcation(*args,**kwargs)
        print('3333333333')
        #  Do something after the call and return the result 
        return result

@DerocatorAsClass
def foo():
    print('in foo()')


foo()

As in the above example, it is also very convenient to use classes as decorators

The above is the python decorator how to use the details, more information about the use of python decorator please pay attention to other related articles on this site!


Related articles: