---1---
假设我们要增强某个
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改某个
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator).
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
---2---
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator.
观察下面的代码:
def log(func):def wrapper(*args, **kw):print('call %s():' % func.__name__)return func(*args, **kw)return wrapper
@log
def now():print('2015-3-25')
把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。
---3---
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):def decorator(func):def wrapper(*args, **kw):print('%s %s():' % (text, func.__name__))return func(*args, **kw)return wrapperreturn decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():print('2015-3-25')
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
:
>>> now.__name__
'wrapper'
因为返回的那个wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:
import functoolsdef log(func):@functools.wraps(func)def wrapper(*args, **kw):print('call %s():' % func.__name__)return func(*args, **kw)return wrapper
或者针对带参数的decorator:
import functoolsdef log(text):def decorator(func):@functools.wraps(func)def wrapper(*args, **kw):print('%s %s():' % (text, func.__name__))return func(*args, **kw)return wrapperreturn decorator
上面的东西看的一知半解的,看完下面的这一段应该会好点:
def check_is_admin(f):def wrapper(*args,**kwargs):if kwargs.get('username') != 'admin':raise Exception("This user is not allowed to get food")return f(*args, **kwargs)return wrapperclass Storage(object):@check_is_admindef get_food(self, username,food):return Storage.get(food)@check_is_admindef put_food(self,username,food):return Storage.put(food)
参考:
(1).https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584(廖雪峰官方文档)
(2).https://www.zhihu.com/question/21408921/answer/129036707 (怎样才能写出 Pythonic 的代码?)
(3).https://www.zhihu.com/question/26930016/answer/99243411 (如何理解Python装饰器?)