04_05、装饰器
一、装饰器的概念
装饰:给函数增加额外的功能
器:就是工具
装饰器不是一个新的知识,而是名称空间,闭包函数、函数嵌套等等的组合就是装饰器
装饰器的核心思想:在"不改变原有函数的代码"和"原有调用方式"的基础上"增加额外"的功能
import time # 引入time模块 # 这是一个index函数 # 现在要给它添加一个功能:给index函数统计执行时间 def index(): time.sleep(2) # 运行阻滞2秒,方便更清晰查看函数的执行时间 print('from index') start_time = time.time() # 在函数执行之前打印一个时间节点 index() # 调用函数,开始运行函数 end_time = time.time() # 等待函数执行完毕之后,再打印一个时间节点 print(end_time - start_time) # 总的执行时间就是两个的差值
# 通过这四行代码给函数index添加了一个统计运行时间的“额外功能” # 而且没有改变函数index的”原有代码“和”调用方式“ # 所以这四行代码就可以说是函数index的装饰器 # 麻烦的地方在于这个装饰器被定死了,只能统计这一个函数的运行时间
二、简易版本的装饰器
在前一段代码中,通过装饰器已经实现了对函数index的运行时间统计。
但是局限性太大,它只能对index这一个函数统计,不能实现对多个函数的重复利用
为此,需要对装饰器进行封装优化
第一种优化方式:
利用实参向形参传递的方式优化
注意:此时改变了函数的调用方式,不能再称之为装饰器
import time # 引入time模块 # 这是一个index函数 # 现在要给它添加一个功能:给index函数统计执行时间 def index(): time.sleep(2) # 运行阻滞2秒,方便更清晰查看函数的执行时间 print('from index') # 这是一个home函数 # 也要给它添加一个统计执行时间得功能 def home(): time.sleep(3) # 运行阻滞2秒,方便更清晰查看函数的执行时间 print('from home') # 通过对代码封装优化,以函数形式实现统计运行时间的功能 def get_time(func): # 通过实参向形参传递的方式,实现给多个函数统计运行时间 start_time = time.time() # 在函数执行之前打印一个时间节点 func() # 调用函数,开始运行函数 end_time = time.time() # 等待函数执行完毕之后,再打印一个时间节点 print(end_time - start_time) # 总的执行时间就是两个的差值 get_time(index) # 把要统计的函数index作为实参写入,并调用,得出函数index的运行时间 get_time(home) # 把要统计的函数home作为实参写入,并调用,得出函数home的运行时间 # 注意这里函数调用方式已经改变,通过get_time调用而不是index或者home本身
第二种优化方式:
在原有功能函数的外围再加一层函数,实现闭包
利用闭包函数传参,可以不改变函数原有代码和调用方式
import time # 引入time模块 # 这是一个index函数 # 现在要给它添加一个功能:给index函数统计执行时间 def index(): time.sleep(2) # 运行阻滞2秒,方便更清晰查看函数的执行时间 print('from index') # 这是一个home函数 # 也要给它添加一个统计执行时间得功能 def home(): time.sleep(3) # 运行阻滞2秒,方便更清晰查看函数的执行时间 print('from home') def outer(func): # 写入形参,内部函数调用此参数,完成函数闭包 def get_time(): start_time = time.time() func() end_time = time.time() print(end_time - start_time) return get_time # 调用get_time完毕后给出返回值,已进入真正的功能模块 index = outer(index) # outer(index)得出返回值get_time,为保证不改变调用方式,给get_time赋值index index() # index()开始调用,由于index是get_time赋值给的,所以是get_time(),调用get_time,可以直接得出运行时间 home = outer(home) # outer(home)得出返回值get_time,为保证不改变调用方式,给get_time赋值home home() # home()开始调用,由于home是get_time赋值给的,所以是get_time(),调用get_time,可以直接得出运行时间
三、进阶版本的装饰器
有参函数的装饰器
之前所有的装饰器实例都是无参函数,无需给函数传值
如果换成有参函数,需要一些变动,否则报错
import time # 引入time模块 # 这是一个有参函数 # 要求统计这个函数的运行时间 def login(name): # 给函数login一个形参name time.sleep(2) print('from login', name) def outer(func): # 写入形参,内部函数调用此参数,完成函数闭包 def get_time(name): start_time = time.time() func(name) end_time = time.time() print(end_time - start_time) return get_time # 调用get_time完毕后给出返回值,已进入真正的功能模块 login = outer(login) # 把要统计的函数login的名称作为实参传给闭包函数outer,以实现统计运行时间的功能 # 得到的返回值为get_time, # 为了保证调用方式相同,把get_time再次赋值给login,此时login就是get_time login('ly') # login()开始调用,就是get_time开始调用,运行函数实现统计运行时间的功能
有参和无参函数都可实现的装饰器
无参函数和有参函数因为参数的不同,代码也需要相应的改变
但是通过*args和**kwargs可以实现二者兼容
import time # 引入time模块 def index(): time.sleep(3) print('这是无参函数index') # 这是一个有参函数 # 要求统计这个函数的运行时间 def login(name): # 给函数login一个形参name time.sleep(2) print('这是有参函数login', name) def outer(func): # 写入形参,内部函数调用此参数,完成函数闭包 def get_time(*args, **kwargs): # 把形参写成(*args, **kwargs)格式,可以同时满足有参和无参函数 start_time = time.time() func(*args, **kwargs) # 调用外部函数时,也要写成(*args, **kwargs)格式,此时需要统计时间的函数开始运行 end_time = time.time() print(end_time - start_time) return get_time # 调用get_time完毕后给出返回值,已进入真正的功能模块 # 无参函数的统计结果 index = outer(index) index() # 有参函数的统计结果 login = outer(login) # 把要统计的函数login的名称作为实参传给闭包函数outer,以实现统计运行时间的功能 login('ly')
四、装饰器的返回值问题
函数的返回值return的特性是:
不写return或者单写return后面不添加值,则返回None
如果写值则返回该值
装饰器的核心功能是最内部空间的运行代码,最内部的函数代码的返回值也是装饰器的返回值
因此不写或单写return,返回的值是None,而不是需要的函数的返回值
解决方式为:
在装饰器结尾给调用的函数重新赋值
在内部空间中return新赋值的变量名
import time # 引入time模块 def index(): time.sleep(1) print('这是无参函数index') return '这是无参函数index' # 这是一个有参函数 # 要求统计这个函数的运行时间 def login(name): # 给函数login一个形参name time.sleep(1) print('这是有参函数login', name) return '这是有参函数login' def outer(func): # 写入形参,内部函数调用此参数,完成函数闭包 def get_time(*args, **kwargs): # 把形参写成(*args, **kwargs)格式,可以同时满足有参和无参函数 start_time = time.time() res = func(*args, **kwargs) # 给调用的函数重新赋值 end_time = time.time() print(end_time - start_time) return res # 函数get_time返回值为需要统计的函数的返回值 return get_time # 调用get_time完毕后给出返回值,已进入真正的功能模块 # 无参函数的统计结果 index = outer(index) res = index() print(res) # 有参函数的统计结果 login = outer(login) # 把要统计的函数login的名称作为实参传给闭包函数outer,以实现统计运行时间的功能 res = login('ly') print(res)
五、装饰器的固定模板
def outer(func): def inner(*args,**kwargs): print('函数执行之前要执行的代码') res = func(*args,**kwargs) print('函数执行之后要执行的代码') return res return inner
六、装饰器的语法糖
1、什么是语法糖
根据之前的实例发现,在装饰器调用阶段
若要调用装饰器每次都需要把装饰器的函数 outer(被装饰函数名) 重新赋值给被装饰的函数名
然后再次给要调用的函数名重新赋值,比较麻烦
函数的语法糖@outer代替了赋值这一功能
装饰器语法糖@,把它下面的函数当参数传入语法糖,并且把返回结果赋值给函数名
2、注意:
@outer必须放在要调用的函数之上,在装饰器之下
把语法糖下面紧贴着的函数名当成参数传递给装饰器函数参数
3、有语法糖的装饰器的写入流程:
装饰器
语法糖
要执行的函数
函数调用
import time # 引入time模块 def outer(func): # 写入形参,内部函数调用此参数,完成函数闭包 def get_time(*args, **kwargs): # 把形参写成(*args, **kwargs)格式,可以同时满足有参和无参函数 start_time = time.time() res = func(*args, **kwargs) # 给调用的函数重新赋值 end_time = time.time() print(end_time - start_time) return res # 函数get_time返回值为需要统计的函数的返回值 return get_time # 调用get_time完毕后给出返回值,已进入真正的功能模块 @outer # 相当于index = outer(index) def index(): time.sleep(1) print('这是无参函数index') return '这是无参函数index'
res = index()
@outer # 相当于login = outer(login) def login(name): # 给函数login一个形参name time.sleep(1) print('这是有参函数login', name) return '这是有参函数login'
res = login('ly')
七、装饰器的双层语法糖
1、双层语法糖
pass
2、三层语法糖练习
pass
八、有参装饰器
pass
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通