目录
装饰器概念
装饰器本质就是函数,功能是为其他函数添加附加功能。装饰器的原则如下
- 不修改被装饰函数的源代码
- 不修改被装饰函数的调用方式
装饰器的实现
要实现装饰器,就是遵循装饰器的原则来实现装饰器,具体做法就是 装饰器=高阶函数+函数嵌套/闭包。
我们用一个例子来实现以下功能。有个函数foo,执行时间较长,我们在不修改foo函数的源代码、不修改foo函数的调用方式的前提下,显示foo函数执行的时间。这个需求就用装饰器来实现,我们一点一点接近装饰器的完整实现。
import time
def foo():
"""
模拟foo函数运行了3秒左右
:return:
"""
time.sleep(3)
print('from foo')
pass
# foo函数的调用方式就是直接调用
foo()
修改被装饰函数的代码(非装饰器实现)
我们可以选择在foo函数开始执行的时候记下时间,在foo结束执行的时候再记下时间,从而得到foo函数的执行时间。但是这样就必须修改foo函数的源代码,这在大型项目里面很可能会引发连锁反应,导致其他调用的地方出错。
import time
def foo():
"""
模拟foo函数运行了3秒左右
:return:
"""
start_time = time.time()
time.sleep(3)
print('from foo')
stop_time = time.time()
print("foo执行时间 %s" % (stop_time - start_time))
pass
# foo函数的调用方式就是直接调用
foo()
# from foo
# foo执行时间 3.0006065368652344
修改被装饰函数的调用方式(非装饰器实现)
我们还可以保持foo函数不修改,将foo作为参数传给装饰器函数timer,由timer来记录foo的执行时间。这个样保证了foo代码不变,但是foo函数的调用方式发生了变化。
import time
def timer(func):
start_time = time.time()
func()
stop_time = time.time()
print("running time: %s" % (stop_time - start_time))
return func
def foo():
"""
模拟foo函数运行了3秒左右
:return:
"""
time.sleep(3)
print('from foo')
pass
timer(foo)
# from foo
# running time: 3.013805627822876
装饰器的实现(不完整的实现level1)
有没有既不修改被装饰函数的源代码、又不修改被装饰函数的调用方式,这样两全其美的实现呢?那就是装饰器。装饰器函数:将被装饰函数作为入参,这就需要定义高阶函数;将附加了装饰功能的函数作为返回值返回,同时保证被装饰函数在装饰器里面不被调用,这就需要函数嵌套/闭包。最后,要保证调用方式不变,那么就将返回值赋给被装饰函数。
具体到被装饰函数foo和装饰器timer的例子。就是foo要作为timer的入参,timer里面定义嵌套函数wrapper,wrapper被调用时foo和修饰功能都被执行,将wrapper作为返回值返回,返回值付给foo。
import time
def timer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print("运行时间 %s" % (stop_time - start_time))
pass
return wrapper
def foo():
"""
模拟foo函数运行了3秒左右
:return:
"""
time.sleep(3)
print('from foo')
pass
foo = timer(foo)
foo()
# from foo
# 运行时间 3.012805223464966
装饰器实现的语法糖(不完整的实现level2)
上面的实现还有一个问题,那么就是每次都要经过装饰器返回值付给被修饰函数的过程。这个Python在语法成名予以解决。那就是把 @装饰器 放在被装饰函数定义的前一行,这样就装饰了。
import time
def timer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print("运行时间 %s" % (stop_time - start_time))
pass
return wrapper
@timer
def foo():
"""
模拟foo函数运行了3秒左右
:return:
"""
time.sleep(3)
print('from foo')
pass
foo()
# from foo
# 运行时间 3.012805461883545
函数闭包加上返回值和参数(基本完整的装饰器实现)
上面实现的装饰器还不够完整。如果被装饰的函数还有入参和返回值,那么上面的实现方式是不行的。我们来解决这个问题。
首先,如果被装饰函数有入参,在执行装饰器返回的嵌套函数里面也要有相应的入参。为了保证装饰器适应性任意多个入参,这个嵌套函数应该有边长的参数,因此wrapper的入参是*args,**kwargs。调用func的时候也应当相应的加参数*args,**kwargs。注意,这两个*args,**kwargs的含义是不一样的。wrapper的参数是函数定义,回忆函数基础那篇博客变长参数的部分,这里的args和kwargs分别是元组和字典。func调用的地方是函数调用的入参,是解压序列,将args元组解压序列成位置参数,将kwargs解压序列成关键字参数。因此,在装饰器实现的时候,都应保证wrapper和func的参数是*args,**kwargs。
其次,如果被装饰函数有返回值,在执行装饰器返回的嵌套函数里面也要有相应的返回值。解决办法就是将返回值记录下来。在适当的地方返回。
import time
def timer(func):
def wrapper(*args, **kwargs): # 这里的*args, **kwargs是函数定义,表示可边长参数
start_time = time.time()
res = func(*args, **kwargs) # 这里的*args, **kwargs是函数调用的入参,将wrapper的参数解压序列
stop_time = time.time()
print("运行时间 %s" % (stop_time - start_time))
return res
pass
return wrapper
@timer
def foo(os, db, app):
"""
模拟foo函数运行了3秒左右
:return:
"""
time.sleep(0.2)
res = 'os->%s,db->%s,app->%s' % (os, db, app)
print('from foo')
return res
pass
RES = foo('linux', db='mysql', app='apache')
print(RES)
# from foo
# 运行时间 0.20280051231384277
# os->linux,db->mysql,app->apache
过渡内容
这一部分为后面做铺垫,例子完全来自我所看视频教程的内容。需求是,在首页index、个人中心home和购物车shopping_car执行前进行身份验证,不能修改这三个函数的调用方式。
def index():
print('welcome to front page')
pass
def home(name):
print('welcome home%s' % name)
pass
def shopping_car(name):
print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))
pass
index()
home('kevin')
shopping_car('kevin')
# welcome to front page
# welcome homekevin
# kevin's shopping car has [food,drink,toy]
用全局变量来模拟数据库记录和当前用户session。代码如下。
# 用户列表,模拟数据库存储用户信息
user_dic = [
{'username': 'alice', 'password': '123'},
{'username': 'kevin', 'password': '123'},
{'username': 'bob', 'password': '123'},
{'username': 'charlie', 'password': '123'},
]
# 当前用户,模拟session
curr_user = {'username': None, 'login': False}
def auth_func(func):
def auth_check(*args, **kwargs):
# 如果有session,直接执行功能
if curr_user['username'] and curr_user['login']:
res = func(*args, **kwargs)
return res
# 否则验证身份
username = input('username:').strip()
password = input('password:').strip()
for user in user_dic:
if username == user['username'] and password == user['password']:
# 身份验证成功,更新session,并执行功能
curr_user['username'] = username
curr_user['login'] = True
res = func(*args, **kwargs)
return res
pass
# 验证不成功,报错
print('wrong username or password!')
pass
return auth_check
@auth_func
def index():
print('welcome to front page')
pass
@auth_func
def home(name):
print('welcome home, %s' % name)
pass
@auth_func
def shopping_car(name):
print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))
pass
index()
home('kevin')
shopping_car('kevin')
# username:kevin
# password:123
# welcome to front page
# welcome home, kevin
# kevin's shopping car has [food,drink,toy]
带参数的装饰器
接上面过渡内容,如果我们的验证方式有不同,index用数据库,home和shopping_car用其他的验证方式,那怎么办呢?可以用带有参数的装饰器。我们在上面auth_func外再包一层auth,再调用装饰器的时候,调用auth执行的结果。代码如下。
# 用户列表,模拟数据库存储用户信息
user_dic = [
{'username': 'alice', 'password': '123'},
{'username': 'kevin', 'password': '123'},
{'username': 'bob', 'password': '123'},
{'username': 'charlie', 'password': '123'},
]
# 当前用户,模拟session
curr_user = {'username': None, 'login': False}
def auth(auth_type='filedb'):
def auth_func(func):
def auth_check(*args, **kwargs):
# 这里通过auth_type可以做更多的选择
if auth_type == 'filedb':
# 如果有session,直接执行功能
if curr_user['username'] and curr_user['login']:
res = func(*args, **kwargs)
return res
# 否则验证身份
username = input('username:').strip()
password = input('password:').strip()
for user in user_dic:
if username == user['username'] and password == user['password']:
# 身份验证成功,更新session,并执行功能
curr_user['username'] = username
curr_user['login'] = True
res = func(*args, **kwargs)
return res
pass
# 验证不成功,报错
print('wrong username or password!')
elif auth_type == 'ldap':
print('ldap check')
res = func(*args, **kwargs)
return res
else:
print('what the fuck!')
pass
return auth_check
# 将auth_func返回
return auth_func
# @ auth调用的结果
# 也就相当于@auth_func
@auth()
def index():
print('welcome to front page')
pass
# @ auth调用的结果
# 也就相当于@auth_func
@auth(auth_type='ldap')
def home(name):
print('welcome home, %s' % name)
pass
# @ auth调用的结果
# 也就相当于@auth_func
@auth(auth_type='kerbers')
def shopping_car(name):
print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))
pass
index()
home('kevin')
shopping_car('kevin')
# username:kevin
# password:123
# welcome to front page
# ldap check
# welcome home, kevin
# what the fuck!
来源:CSDN
作者:苦行僧(csdn)
链接:https://blog.csdn.net/qpeity/article/details/103758879