Python装饰器
装饰器模式是一个强大的模式,可以给一个函数增加额外的方法而不用修改原来的代码。比如,当我们需要给原来的代码添加日志记录时,需要将原来的代码进行修改,在函数里面增加日志记录的代码。但如果使用装饰器模式,使用装饰器装饰原来的代码,将日志记录放在装饰器里,就可以避免修改原有代码,同时又实现了所需的功能。这种编程模式就叫做面向切面编程。
日志记录实例
比如,我们有一个函数func
:
def func():
print('func')
现在,我们需要记录一下这个函数执行时的日志记录,即记录一下函数执行的时间,输出执行时间和方法名。日志输出函数如下:
from datetime import datetime as dt
def log(func):
print('['+str(dt.now())+']'+func.__name__)
func()
好了,当我们想使用func
函数的时候该怎么做呢?不是直接func()
,而是如下:
log(func)
那么,会输出:
[2017-09-10 20:51:09.970854]func
这就实现了调用func
时,同时进行日志记录。但这样做很明显会有问题,就是我们需要把所有调用func
的地方改为log(func)
,这很麻烦,并且会需要改很多代码。所以,我们换一种写法:
def log(func):
def wrapper():
print('['+str(dt.now())+']'+func.__name__)
func()
return wrapper
请注意,这就是一个装饰器了!
里面那个wrapper
的意思就是装饰、包裹的意思。我们将func
函数装饰一下变成了一个新的函数然后返回它。那该如何使用呢?如下:
func = log(func)
func()
使用方法就是用log装饰器将func装饰一下后返回覆盖掉原有的func即可。
装饰器语法糖
所谓的语法糖的意思就是添加一个语法,让代码更简单的意思。Python为装饰器也提供了一个语法糖,就是@
符号。我们可以使用这个符号声明这是一个装饰器。如下,我们使用语法糖的写法给func
添加log
这个装饰器:
@log
def func():
return print('func')
这样写好后,直接调用func()
即可同时输出日志了:
[2017-09-10 21:10:50.933021]func
func
装饰器给函数传参
我们把func
函数改一改:
def func(msg):
print("-->" + msg)
现在,我们要给函数输入参数了,而原来那个log
装饰器是没有带参数的,所以用不了了。下面,我们就要实现一个可以给函数传参数的装饰器:
def log(func):
def wrapper(msg):
print('['+str(dt.now())+']'+func.__name__)
return func(msg)
return wrapper
这样一改,log
就又可以用了,给func
再加上语法糖:
@log
def func(msg):
print("-->" + msg)
# output
func('hello')
output:
[2017-09-10 21:16:26.677027]func
-->hello
这样一改,装饰器就又可以用了。那如果我再给func
又加一个参数呢?再加一个参数呢?我如果给func
加不定数个参数呢?又该怎么做呢?再改log
吗?我知道大家心里肯定也意识到了这个方法的不靠谱。
别着急,下面我们就写一个更强力的log
装饰器,来让装饰器可以装饰任何函数,而不用管那个函数又多少个参数。使用Python的可变参数*args
和关键字参数**kwargs
即可。修改log
,如下:
def log(func):
def wrapper(*args, **kwargs):
print('['+str(dt.now())+']'+func.__name__)
return func(*args, **kwargs)
return wrapper
带参数的装饰器
装饰器本身也可以带参数,比如说,我们给log
装饰器添加一个参数is_show
用于是否显示日志输出。如下:
def log(is_show=True):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
if is_show:
print('['+str(dt.now())+']'+func.__name__)
return func(*args, **kwargs)
return inner_wrapper
return wrapper
作为对比,我们写两个func
:
# 默认显示日志
@log(True)
def func1():
print("func1")
# 默认不显示日志
@log(False)
def func2():
print("func2")