装饰器,见名知意,“装饰”表示为被装饰对象添加新的功能,“器”则表示是一个工具,它不是一个新的知识,而是“函数嵌套+闭包+函数对象“等内容进行组合使用的产物,目的是为了在不改变原有函数代码内容和调用方式的基础上,新增额外的功能。例如认证功能,在很多地方都需要使用,有了装饰器,就可以节省很多代码,使其在很多地方能够重复使用。
装饰器的核心思想:
储备知识:
time模块
时间戳:表示执行的这一刻距离1970年一月一日的总秒数
time.time()
原地等待:
time.sleep(3) # 表示等待三秒
import time
def index():
time.sleep(3)
print('from index')
# 1. 在函数执行之前打印一个时间节点
start_time = time.time()
index()
# 2. 等待函数执行完毕之后,在打印一个时间节点
end_time = time.time()
# 3. 总的执行时间就是两个的差值
print(end_time - start_time)
在上面引例中,虽然实现了统计index函数执行时间的功能,但是如果需要统计别的函数的执行时间时,就需要重复写同样的代码,这样很不方便,因此我们通过装饰器来实现此功能。
代码实现:
import time
def index():
time.sleep(2)
print('from index')
def outer():
func = index
def get_time():
# 1. 统计一下函数执行前的时间戳
start_time = time.time()
func()
# 2. 统计一下函数执行完之后的时间戳
end_time = time.time()
# 3. 计算差值
print('函数执行时间:%s' % (end_time - start_time))
return get_time
index = outer()
index()
以上代码通过装饰器的方式实现了统计函数index的执行时间,但是被统计的函数已经固定了,为了让它通用,变成“器”,我们采用传参的方式将被装饰的函数传入,因此有了下面的结果:
import time
def index():
time.sleep(2)
print('from index')
def home():
time.sleep(3)
print('from home')
def outer(func):
def get_time():
# 1. 统计一下函数执行前的时间戳
start_time = time.time()
func()
# 2. 统计一下函数执行完之后的时间戳
end_time = time.time()
# 3. 计算差值
print('函数执行时间:%s' % (end_time - start_time))
return get_time
index = outer(index) # index => get_time的内存地址
index() # get_time()
home = outer(home)
home()
这样我们就实现了装饰器的简易版本,可以成为了通用的工具,需要统计哪个函数的执行时间,将其传入即可。
在装饰器简易版本的基础上,有时候被装饰的函数需要传入参数,因此需要解决传参问题。
通过观察,在原函数index()
被装饰后,调用时index名字没有发生变化,但是本质上是执行的是get_time()
的内容,因此我们在执行时传的参数,只需要在get_time()
函数定义时设置形参(*args、**kwargs)来接收即可,接收到真正的参数,在执行func()
函数时,通过*、**
号在实参中的使用,将其解压传给原本被装饰的函数,因此解决了传参的问题,得到了下面的装饰器的进阶版本:
实现代码:
def index(x):
print(x)
def outer(func):
def get_time(*args, **kwargs):
# 1. 统计一下函数执行前的时间戳
start_time = time.time()
func(*args, **kwargs) # index('hhh')
# 2. 统计一下函数执行完之后的时间戳
end_time = time.time()
# 3. 计算差值
print('函数执行时间:%s' % (end_time - start_time))
return get_time
index = outer(index) # index => get_time的内存地址
index('hhh') # get_time('hhh')
在装饰器进阶版本进阶版本的基础上,有时候被装饰函数需要有返回值,因此就需要解决返回值的问题。
通过观察,真正的原来函数是在get_time()
中的func()
执行,若要得到其返回值,因此我们通过一个变量res
接收其返回值,再通过return
将其作为get_time()
的返回值返回即可。
实现代码:
def login(name):
time.sleep(1)
print('from home', name)
return 'from login'
def outer(func):
def get_time(*args, **kwargs):
# 1. 统计一下函数执行前的时间戳
start_time = time.time()
res=func(*args, **kwargs) # login()
# 2. 统计一下函数执行完之后的时间戳
end_time = time.time()
# 3. 计算差值
print('函数执行时间:%s' % (end_time - start_time))
return res
return get_time
login = outer(login) # login => get_time的内存地址
res=login('mark') # get_time()
print(res)
# 调用index函数,调用之前,需要输入用户名和密码,并且,用户名和密码必须输入正确
def index():
print('from index')
def home():
print('from home')
def login():
print('from login')
is_auth = {'is_login': False}
def login_auth(func):
def auth():
# 判断是否已经认证成功了
if is_auth.get('is_auth'):
res = func()
return res
# 1. 让用户输入用户名和密码
username = input('username:').strip()
password = input('password:').strip()
# 2. 判断用户名和密码
if username == 'mark' and password == '123':
func()
is_auth['is_login'] = True
else:
print('输入错误,不能调用函数')
return auth # 一定不能忘记把内层函数的函数名返回出去
index = login_auth(index)
index() # auth()
home = login_auth(home)
home()
login = login_auth(login)
login()
def outer(func):
def inner():
print('函数执行之前要执行的代码')
res = func()
print('函数执行之后要执行的代码')
return res
return inner
为了简洁而优雅的使用装饰器,Python提供了专门的语法来替代index=outer(index)
的形式,通过在被装饰对象的上方单独起一行添加@outer即可,称这种方式为语法糖。
语法糖的执行流程:当装饰器解释到@outer时,就会调用outer函数,并且将紧贴在下方的函数名当作实参传入,然后将返回的结果重新赋值给原函数名。
示例代码:
def outer(func):
def inner():
start_time = time.time()
res = func()
end_time = time.time()
print('函数执行时间:%s' % (end_time - start_time))
return res
return inner
# 统计函数的执行时间
@outer # 底层为 index = outer(index)
def index():
time.sleep(3)
print('from index')
index()
@outer # 底层为 home = outer(home)
def home():
print('from home')
home()
装饰器其实可以叠加多个,我们以两层装饰器叠加为例进行分析,多于两层的装饰器叠加同理推导即可。
代码示例:
# 统计函数执行时间的装饰器
import time
def get_time(func):
def inner():
start_time = time.time()
res = func()
end_time = time.time()
print('函数执行时间:%s' % (end_time - start_time))
return res
return inner
# 认证装饰器
def login_auth(func):
# func = index
def auth():
# 1. 让用户输入用户名和密码
username = input('username:').strip()
password = input('password:').strip()
# 2. 判断用户名和密码
if username == 'ly' and password == '123':
func() # inner()
else:
print('输入错误,不能调用函数')
return auth # 一定别忘记把内层函数的函数名返回出去
@login_auth # index =login_auth(inner) #index => auth的内存地址
@get_time # inner = get_time(index)
def index(): # def auth()
time.sleep(2)
print('from index')
index() # auth()
# 判断七句print执行顺序
def outter1(func1):
print('加载了outter1')
def wrapper1(*args, **kwargs):
print('执行了wrapper1')
res1 = func1(*args, **kwargs)
return res1
return wrapper1
def outter2(func2):
print('加载了outter2')
def wrapper2(*args, **kwargs):
print('执行了wrapper2')
res2 = func2(*args, **kwargs)
return res2
return wrapper2
def outter3(func3):
print('加载了outter3')
def wrapper3(*args, **kwargs):
print('执行了wrapper3')
res3 = func3(*args, **kwargs)
return res3
return wrapper3
@outter1 # index = outter1(wrapper2) => wrapper1 = outter(wrapper2)
@outter2 # wrapper2 = outter2(wrapper3)
@outter3 # wrapper3 = outter3(index)
def index():
print('from index')
index() # 本质为wrapper1()
# 执行结果为:
加载了outter3
加载了outter2
加载了outter1
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
总结:
多层装饰器叠加时,加载顺序为:从下至上,最贴近被装饰函数的先加载。
执行顺序为:从上至下,最贴近被装饰函数的最后执行。
假如我们需要通过对认证装饰器中的数据来源进行选择,因此我们需要进行额外的参数source_data
传入,但是login_auth()
和auth()
的参数都有特定的作用,不能用来接收其他类别的额外参数,因此我们可以通过在外层再包一层函数,通过闭包函数的方式进行传参,得到了下面的有参装饰器。
代码实现:
# 认证装饰器
def outter(source_data, a, b,c):
# source_data = 'file'
def login_auth(func):
# func = index
def auth( *args, **kwargs):
# 1. 让用户输入用户名和密码
# username = input('username:').strip()
# password = input('password:').strip()
if source_data == 'file':
print('从文件中读数据')
elif source_data =='mysql':
print('从mysql中读取数据')
elif source_data == 'oracle':
print('从oracle中读取数据')
return auth # 一定别忘记把内层函数的函数名返回出去
return login_auth
@outter('file', 1, 2,3)
# 函数名加括号优先执行,outter('file',1,2,3)执行后返回值为login_auth
# 因此outter('file', 1, 2,3) 的本质结果就是 @login_auth, 语法糖结构未被破坏,同时实现了额外参数的传入
def index():
pass
index()