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