引言
点这里看原文
趁着闲暇之际总结了关于装饰器的知识,并结合前人的总结,整理了一篇关于python
装饰器的小文章,接下来分享一下我对python装饰器的理解:简单来说,装饰器是可
调用的对象,它的参数就是一个函数。要么装饰器处理这个函数,然后把它返回,或
者把原来的函数替换成另外的可调用对象。
python中的装饰器是什么
理解一下什么是闭包
def list_append():
l = [] # 1
def worker(new_e): # 2
l.append(new_e) # 3,l在这成了自由变量(w.__code__.co_freevars)
return l # 4
return worker
if __name__ == '__main__':
w = list_append()
for i in range(3):
print(i, ':', w(i))
执行结果:
0 : [0]
1 : [0, 1]
2 : [0, 1, 2]
闭包: #1 到 #4之间的代码
写个简单的装饰器
代码
def get_result(func):
def warp(*args, **kwargs):
print('haha warp')
result = func(*args, **kwargs)
print('result:', result)
return result
return warp
@get_result
def add(a, b):
print('func add:', a + b)
return a + b
if __name__ == '__main__':
add(1, 2)
执行结果及解释
执行结果:
haha warp
func add: 3
result: 3
解释:
add会作为参数func传给get_result函数,然后get_result返回warp函数。
如果你执行add.__name__你会发现明明是add名字却变成了warp,其实这是
python解释器在背后吧warp赋值给了add,然后add就保存了warp的索引,每
次调用add(a, b)都是在执行warp(a, b), 还记的前边说的把函数转变成了
其他的可调用对象,然后二者接收同样的参数。可能有时我们并不想这么干,因
为毕竟只是装饰一下我的函数,结果给我变了属性,怎么解决后边会说。不管怎
么说我们还是实现了一个简单的装饰器。
解决name属性被修改的问题
代码
from functools import wraps
def get_result(func):
@wraps(func)
def warp(*args, **kwargs):
print('haha warp')
result = func(*args, **kwargs)
print('result:', result)
return result
return warp
@get_result
def add(a, b):
print('func add:', a + b)
return a + b
if __name__ == '__main__':
a = add
a(1, 2)
print('name:', a.__name__)
if __name__ == '__main__':
a = add
a(1, 2)
print(a
.__name__)
执行结果及解释
执行结果:
haha warp
func add: 3
result: 3
name: add
解释:
我们用到了functools.warps它的作用就是协助构建良好的装饰器,就是把被修饰的函数对象
的指定属性复制给包装函数对象。可以看一下wraps的部分代码(太占篇幅注释去了):
WRAPPER_ASSIGNMENTS = (
'__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
WRAPPER_UPDATES = ('__dict__',)
# 看到WRAPPER_ASSIGNMENTS、WRAPPER_UPDATES和注释1/2应该已经很明了了
def update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
# 注释1
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
pass
else:
setattr(wrapper, attr, value)
# 注释2
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
wrapper.__wrapped__ = wrapped
return wrapper
def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
return partial(
update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
小结:
当我们需要保留被修饰的函数的属性时,wraps装饰器非常有用,以至于我们在写装饰器的时候
绝大部分情况时需要用到它的,另外上边的例子其实很容易变形成记录日志功能的装饰器,可以
试一下。
鉴权装饰器
场景说明
我们在进行接口开发的过程中,总是免不了对调用方进行鉴权的,毕竟资源时很宝贵的,不能随随
便便让别人拿到。我们又不可能去所有的接口去实现同样的代码部分,这时候装饰器是很好的选择
[参考](https://yunsonbai.top/2016/05/26/decorator/)
代码
from functools import wraps
def user_auth(func):
@wraps(func)
def warp(self, data, *args, **kwargs):
token = data.get('token', False)
if not token:
return {}
return func(self, data, *args, **kwargs)
return warp
class UserInfo(object):
@user_auth
def get_info(self, data):
'''
data: 调用方传递的用户的凭证信息包括token
'''
info = {'age': 18}
return info
if __name__ == '__main__':
user_info = UserInfo()
data = {'token': 'token'}
print('has token:', user_info.get_info(data))
print('no token:', user_info.get_info({}))
执行结果及解释
结果:
has token: {'age': 18}
no token: {}
解释:
这个修士器可以对接口进行鉴权工作,而且不用每个接口中加同样的代码,直接在函数上加上
修饰器即可,另外一定要记得添加self参数,正如前边所说装饰器使得返回的函数和被修饰的
函数接收一样的参数,而用过wraps之后返回的函数被重新赋予了被修饰函数的一些属性.另
外也可以在这附上self.user_id等属性方便后边使用.
传参装饰器
怎么生成传参装饰器
装饰器工厂函数,在学习python过程中经常听到这个词,因为这个函数的存在我们能很容易的
写出传参装饰器,简单来说,这个函数的功能就是把参数传给这个装饰器工厂函数,然后返回装
饰器,再来装饰要装饰的函数.可能不太好捋清楚,来个例子
代码
from functools import wraps
def user_auth(has_token=True):
def decorator(func):
@wraps(func)
def warp(self, data, *args, **kwargs):
if has_token:
token = data.get('token', False)
if not token:
return {}
else:
uid = data.get('uid', False)
passwd = data.get('passwd', False)
if (bool(uid) and bool(passwd)) is False:
return {}
return func(self, data, *args, **kwargs)
return warp
return decorator
class UserInfo(object):
@user_auth(has_token=True)
def get_info(self, data):
'''
data: 调用方传递的用户的凭证信息包括token
'''
info = {'age': 18}
return info
@user_auth(has_token=False)
def login(self, data):
'''
data: 登录信息
'''
info = {'age': 18}
return info
if __name__ == '__main__':
user_info = UserInfo()
print('-------------login------------')
data = {'uid': 'uid1', 'passwd': 'sss'}
print('has uid passwd:', user_info.login(data))
print('no uid passwd:', user_info.login({}))
print('-------------get_info------------')
data = {'token': 'token'}
print('has token:', user_info.get_info(data))
print('no token:', user_info.get_info({}))
执行结果及解释
结果:
-------------login------------
has uid passwd: {'age': 18}
no uid passwd: {}
-------------get_info------------
has token: {'age': 18}
no token: {}
解释:
这个用户鉴权装饰器应对于登陆和登陆后的鉴权,如参数has_token,当为False时,说明
是登陆,反之说明已经登陆成功,应携带token参数请求接口,其他的逻辑应该很简单.