一、装饰器的概念
通俗讲:本质是函数,为其它函数添加附加功能,算是高阶函数!
原则:1、不能修改被装饰的函数的结构!
2、不能修改被装饰的函数的调用方式!
简言之:python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数!
好处:就是在不用更改原函数的代码前提下给函数增加新的功能!
二、使用方式
需求1
# (1)定义一个函数,在不修改源代码的前提下,在不修改函数代码的前提下,对函数的功能进行拓展
def fun1():
print('登陆成功')
# (2)#2.定义一个高级函数(闭包)实现对f1()函数进行权限验证!
def fun2(fun1):
def fc():
print("首先对fun1函数权限进行验证")
fun1() #实际功能函数模块!
print("fun1函数已经处理完毕了")
return fc
# (3)函数的调用-->实现对fun1()函数调用的权限验证!
# 说明:这个过程一定要好好体会!
# 这里传递的是函数名,并没有传递参数!
t = fun2(fun1)
t()
"""
首先对fun1函数权限进行验证
登陆成功
fun1函数已经处理完毕了
"""
进一步简化
def wrapper(fun1):
def fc():
print("首先对fun1函数权限进行验证")
fun1()
print("fun1函数已经处理完毕了")
return fc
# 等价方式--->fun1=wrapper(fun1)-->返回同一个接口!
# 作用--->添加功能并保持原函数名不变!
# Python中支持了@语法糖-->帽子
@wrapper
def fun1():
print('登陆成功')
# (3)函数的调用
fun1()
"""
首先对fun1函数权限进行验证
登陆成功
fun1函数已经处理完毕了
"""
理解:函数和函数调用的区别
需求2:装饰器实现一个函数计时器
# 说明:统计
import time
import string
import random
import functools
li = [random.choice(string.ascii_letters) for i in range(1000)]
def timewc(fun):
def wapper(*args, **kwargs):
# 在函数的执行之前
# 当前时间-->秒
start_time = time.time()
# 执行函数-->统计它的执行时间!
res = fun(*args, **kwargs)
# 在函数执行之后
end_time = time.time()
# 注意:默认是保留6位小数!
print('运行的时间为:%.6f' % (end_time - start_time))
return res
return wapper
@timewc
def con_add():
# print('+'.join(li)) -->等价方式!
s = ''
for i in li:
s += (i + '+')
print(s)
con_add()
需求3:性能比较(完成相同的时间耗时)
@timeit
def fun_list(n):
"""这是fun_list函数,被timeit装饰"""
return [2 * i for i in range(n)]
@timeit
def fun_map(n):
"""这是fun_map函数,被timeit装饰"""
return list(map(lambda x:x*2,range(n)))
# 说明:部分代码省略(见上)
fun_list(5000)
fun_map(5000)
需求4:打印日志
"""
# 创建装饰器, 要求如下:
# 1. 创建add_log装饰器, 被装饰的函数打印日志信息;
# 2. 日志格式为: [字符串时间] 函数名: xxx,
运行时间:xxx, 运行返回值结果:xxx
"""
import time
import functools
#print(time.ctime())
def add_log(func):
# 说明:这里对帮助文档和函数名称又进行了包装,确保获取的是源函数的信息!
# 细节-->接受参数的问题(一般参数到元组中,关键字参数到字典中)
@functools.wraps(func)
def wrapper(*args,**kwargs):
"""warpper函数的帮助文档"""
start_time = time.time()
res = func(*args,**kwargs)
end_time = time.time()
# "时间字符串"-->time.ctime()
# 注意是长"__",并且需要倒包,否则就是包装函数的信息!
# "函数的名字"-->func.__name__
print('[%s] 函数名:%s,运行时间:%.6f,运行返回值的'
'结果:%d' %(time.ctime(),func.__name__,
end_time-start_time,res))
return res
return wrapper
@add_log
def add(x,y):
"""add源函数的帮助文档"""
# 只是为了效果说明
time.sleep(1)
return x+y
result=add(1,10)
# 理解-->实际调用的是wrapper(1,10)
# 注意观察:倒包前后所显示的帮助文档信息和函数名称!
print(add.__name__)
解决1:被装饰的函数有返回值的时候怎么办!
解决2:被装饰的函数如何保留自己的函数名和帮助信息文档!
思考:多个装饰函数装饰同一个函数?
# 多个装饰器-->执行先后顺序
def deorator_a(fun):
print('A')
def inner(*args, **kwargs):
print(args)
print('B')
return fun(*args, **kwargs)
print('C')
return inner
def deorator_b(fun):
print('D')
def outer(*args, **kwargs):
print(args)
print('E')
return fun(*args, **kwargs)
print('F')
return outer
## 注意:代码的执行是从上到下执行!
@deorator_a
@deorator_b
def f(x):
print('G')
return x * 2
# 函数的执行
print(f(1))
结果
D
F
A
C
(1,)
B
(1,)
E
G
2
分析
"""
分析1:代码是自上而下执行的,由于全局只有一个线程,所以只设计代码的跳转问题,并不设计代码的阻塞问题!
分析2:相当于 f=decorator_a(decorator_b(f)) --> f(1)
执行过程
f=decorator_a(decorator_b(f)) --->只是对decorator_b和decorator_a函数的调用,返回inner
f(1) --->对decorator_a函数的内部函数inner进行调用,尽而调用decorator_b函数的内部函数outer,最后调用真实的f(1)函数!
temp=decorator_b(f) -->outer -->fun=f
f=decorator_b(temp) -->inner -->fun=outer
f(1)
明确:函数只有在调用的时候才执行!
实际:先执行相关代码,然后才调用函数!
注意:要明确调用的函数(内层还是外层),只有传参的那一刻才是调用内层函数!
"""
说明:可以通过Debug的调试模式,来看代码的跳转来加以理解!
多个装饰器的应用
"""
已知:['root','admin','redhat']
模拟多个装饰器的应用场景:会采用多个装饰器先验证是否登陆成功,再验证登陆权限是否足够(是不是root)!
"""
import inspect
import functools
# 权限校验模块!
def is_admin(fun):
@functools.wraps(fun)
def wrapper(*args,**kwargs):
# inspect.getcallargs会返回一个字典,
# key值:形参 value:对应的实参数
inspect_res = inspect.getcallargs(fun,*args,**kwargs)
print('inspect的返回值是:%s' %(inspect_res))
# (1)登陆用户是不是root-->权限足够!
# 注意:为什么调用key=name -->底层形参名!
if inspect_res.get('name') == 'root':
temp = fun(*args,**kwargs)
return temp
else:
# (2)权限够不够(是不是root)
print('not root user,no permisson add user')
return wrapper
# 用户名的数据库的模拟
login_session = ['root','admin','redhat']
# 用户登陆
def is_login(fun):
@functools.wraps(fun)
def wrapper(*args,**kwargs):
# 核心:判断登陆的用户存不存在数据库中(模拟)
if args[0] in login_session:
# 说明:一旦进入就表明登陆成功,此时进行权限校验的判断!
temp = fun(*args,**kwargs)
# 注意:这里返回的是函数名-->没有意义(因为底层并没有返回数值!)!
return temp
else:
print('Error:%s 没有登陆成功' %(args[0]))
return wrapper
@is_login
@is_admin
def add_user(name):
print('add user~')
# 调用函数
add_user('root')
思考:如果装饰器想携带参数怎么办?
import functools
import time
def log(kind): #装饰器
def add_log(func):# #装饰器加参数需要多加一层嵌套
@functools.wraps(func)
def wrapper(*args,**kwargs):# 为了兼容各类函数参数,添加 *args,**kwargs 不固定参数
start_time = time.time()
res = func(*args,**kwargs)
end_time = time.time()
print('<%s>[%s] 函数名:%s,运行时间:%.6f,运行返回值的'
'结果:%d' %(kind,time.ctime(),func.__name__,
end_time-start_time,res))
return res
return wrapper
return add_log
@log('debug')
def add(x,y):
time.sleep(1)
return x+y
print(add(1,2))
小结
关于多个装饰器修饰一个函数总结要点:
1.当一个函数被多个装饰器装饰时,装饰器的加载顺序是从内到外的(从下往上的)。
理解:装饰器是给函数装饰的,所以要从靠近函数的装饰器开始从内往外加载!
2.外层的装饰器,是给里层装饰器装饰后的结果进行装饰。
相当于:外层的装饰器装饰的函数是里层装饰器的装饰原函数后的结果函数(装饰后的返回值函数)
三、装饰器的应用场景
可以用到装饰器的地方有很多,简单的举例如以下场景
- 引入日志
- 函数执行时间统计
- 执行函数前预备处理
- 执行函数后清理功能
- 权限校验等场景
- 缓存
1. 装饰器本质上是一个高级Python函数,通过给别的函数添加@标识的形式实现对函数的装饰
2.装饰器的功能:
它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
四、装饰器的相关参考