装饰,《辞源》解释为“装者,藏也,饰者,物既成加以文采也。”指的是对器物表面添加纹饰、色彩以达到美化的目的。
python里的装饰器本质是一个函数,一个可以接受函数作为参数,返回值是函数的函数。它可以不改变一个函数代码的情况为这个函数提供额外功能,从而起到美化和重用代码作用。主要在用户验证、,插入日志、事物处理、缓存场景中使用。
例如,现在有两个函数代码如下:
def foo():
''' This is foo function '''
print 'I am foo'
def bar():
''' This is bar function '''
print 'I am bar'
现在需求有变更,需要给foo、bar两个函数添加日志来记录函数名字,防止以后出现问题便于定位问题:
def foo():
''' This is foo function '''
logging.warn('foo is running' )
print 'I am foo'
def bar():
''' This is bar function '''
logging.warn('bar is running')
print 'I am bar'
看可以上面那段代码出现了两次loggin.warn调用,每次需要改动原有函数代码,但是装饰器代码更简洁:
装饰器
def log(func):
def wrapper(*args, **kwargs):
logging.warn('%s is running % func.__name__)
return func(*args, **kwargs)
return wrapper
@log
def foo():
''' This is foo function '''
print 'I am foo'
@log
def bar():
''' This is bar function '''
print 'I am bar'
@是python一种语法糖,可以在被装饰函数上面用@加装饰器名,来使用装饰器。但是装饰器装饰函数,会创建一个动态函数来替换原来的,然而,新函数缺少会很多原函数的属性,如docstring和名字。幸好,万能的python提供functions包wraps函数来解决这个问题,修改后代码如下:
def log(func):
@functions.wraps(func) # 多了这行代码
def wrapper(*args, **kwargs):
logging.warn('%s is running % func.__name__)
return func(*args, **kwargs)
return wrapper
@log
def foo():
''' This is foo function '''
print 'I am foo'
@log
def bar():
''' This is bar function '''
print 'I am bar'
另外,如果传入被修饰函数带有位置参数和关键字参数,可以使用inspect.getcallargs提供更加智能处理方法,不用判断某个参数是位置参数还是关键字参数。只需要在inspect.callcargs返回参数名字-值为键值对的字典中查找即可,代码如下:
def log(func):
@functions.wraps(func)
def wrapper(*args,**kwargs):
func_args = inspect.getcallargs(func, *args, **kwargs) # 使用inspect内省功能获取参数
if func_args.get('username') != 'admin':
raise Exception('This user can not modify data')
return func(*args, **kwargs)
return wrapper