This article will make you proficient in using python decorators

1. Function decorator

First, let's look at a very simple example. Suppose there is a test. The code is very simple and looks like the following.

def test():
    print("I am", test.__name__)
    pass

However, there is now a requirement, that is, each time the test function is executed, the user needs to be told which function is being executed, so the code becomes:

def test():
    print("I am", test.__name__)
    print("%s is running" % test.__name__)
    pass

test()

'''
out: 
I am test
test is running
'''

What if there are test1, test2, ... many functions with different names? Do I have to add print(...is running) to each function? Obviously it doesn't work. What should I do? Methods as below. This is because python can pass the function name as a parameter of the function, and test is passed to the decorator as a parameter. This simplifies the code

def decorator(func):
    func()
    print("%s is running" % func.__name__)
    print("ok")

def test():
    print("I am", test.__name__)
    pass


def test1():
    print("I am", test1.__name__)
    pass


decorator(test)
decorator(test1)
'''
out:
I am test
test is running
ok
I am test1
test1 is running
ok
'''

This seems fine, but a big problem is that what we call every time is not the test function, but the decorator function. Test is the function we need to call, so what is the solution? That's the decorator. Let’s change the above. as follows:

def decorator(func):
    print(func.__name__)
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print("%s is running" % func.__name__)
        print("ok")
    return wrapper


def test():
    print("I am", test.__name__)
    pass


def test1():
    print("I am", test1.__name__)
    pass


test = decorator(test)
test()

test1 = decorator(test1)
test1()
'''
out:
test
I am wrapper
test is running
ok
test1
I am wrapper
test1 is running
ok
'''

Looking at the above results, you will find a problem. When executing test and test1, the function name is actually warpper. This is because test = decorator(test) and test1 = decorator(test1) have changed the function name to warpper. , so how to avoid this modification. This is generally done by using wraps(func) to decorate the wrapper

from functools import wraps
def decorator(func):
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print("%s is running" % func.__name__)
        print("ok")
    return wrapper


def test():
    print("I am", test.__name__)
    pass


def test1():
    print("I am", test1.__name__)
    pass


test = decorator(test)
test()

test1 = decorator(test1)
test1()
'''
out:
I am test
test is running
ok
I am test1
test1 is running
ok
'''

Is there a simpler way to write it? Yes, it is the symbol @, as shown below. @decorator is equivalent to test = decorator(test). This is the complete template way to write the decorator. The test and test1 functions are still exposed to users. If they have parameters, they can be passed normally.
The decorator actually executes @decorator first, that is, executes the decorator function first, and then executes the wrapper function. Just remember this order.

from functools import wraps
def decorator(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print("%s is running" % func.__name__)
        print("ok")
    return wrapper

@decorator
def test():
    print("I am", test.__name__)
    pass

@decorator
def test1():
    print("I am", test1.__name__)
    pass


test()
test1()
'''
out:
I am test
test is running
ok
I am test1
test1 is running
ok
'''

2. An example of decorator utilization

If there is a need to record the log of each function to a file, including error logs, and the log needs to be printed to the console, let's see how to write it.

import logging
from logging import handlers
from functools import wraps
import traceback


class Logger(object):

    level_relations = {
    
    
        'debug':logging.DEBUG,
        'info':logging.INFO,
        'warning':logging.WARNING,
        'error':logging.ERROR,
        'crit':logging.CRITICAL
    }

    def __init__(self, filename, level='info', when='D', backcount=3,
                 fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
        self.logger = logging.getLogger(filename)
        self.logger.setLevel(self.level_relations.get(level))
        format_str = logging.Formatter(fmt)
        sh = logging.StreamHandler()
        sh.setFormatter(format_str)
        th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backcount, encoding='utf-8')
        th.setFormatter(format_str)
        self.logger.addHandler(sh)
        self.logger.addHandler(th)

    @staticmethod
    def catch_except(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as ee:
                log.logger.error(traceback.format_exc())
                raise ee
        return wrapper


@Logger.catch_except
def test(a):
    log.logger.info("start calculate...")
    a = a + 1


log = Logger(filename="tmplog.txt")
test("li")

The files in the log are as follows:

INFO: start calculate...
ERROR: Traceback (most recent call last):
  File "xx\decorator.py", line 34, in wrapper
    return func(*args, **kwargs)
  File "xx\decorator.py", line 44, in test
    a = a + 1
TypeError: can only concatenate str (not "int") to str

Guess you like

Origin blog.csdn.net/m0_59156726/article/details/132169810