一文让你熟练使用python装饰器

1. 函数装饰器

首先看一个非常简单的例子,假设有一个test,代码很简单是下面这个样子。

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

但是,现在有一个需求,就是每次执行test函数时需要告诉用户具体哪个函数在执行,于是代码变成:

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

test()

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

假如,有test1, test2, … 很多不同名字的函数呢,难道每一个函数我都要加一句print(…is running),显然不行,怎么办呢?方法如下。这是由于python可以把函数名当作函数的参数传递,test作为参数传递给了decorator。如此便实现了代码的简化

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
'''

这样子看起来没问题,但是有一个大问题就是我们每次调用的不是test函数,而是decorator函数。test才是我们需要调用的函数,那有什么办法解决呢?那就是装饰器。我们把上面的改一改。如下:

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
'''

看上面的结果你会发现一个问题,在执行test,test1时函数名竟然时warpper,这是因为test = decorator(test), test1 = decorator(test1)这两个已经把函数名称给改成warpper了,那如何避免这种修改呢。一般这样做,使用wraps(func)来装饰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
'''

有没有简单一点的写法,有,就是@这个符号,如下,@decorator 等价于test = decorator(test), 这就是装饰器完整的模板写法。暴露给用户的仍然是test 和test1函数,如果他们有参数,正常传递即可。
装饰器实际上先执行@decorator也就是先执行decorator函数,在执行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

@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. 一个装饰器利用的例子

假如有一个需求,想要记录每一个函数的日志到文件里面,包括报错日志,而且需要把日志打印到控制台,我们们看看怎么写呢。

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")

日志中的文件如下:

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

猜你喜欢

转载自blog.csdn.net/m0_59156726/article/details/132169810