前言
在Python中,decorator(装饰器)是一种特殊的函数,主要用于修改或增强其他函数的功能。它可以在不修改原函数代码的情况下,通过在原函数的定义之前使用@语法糖来对其进行修饰。
Decorator装饰器的作用
Decorator的作用有以下几个方面:
1. 添加额外的功能:通过在原函数周围包裹一层装饰器函数,可以在原函数执行前后执行一些额外的代码,比如日志记录、性能分析、异常处理等。
2. 修改函数的行为:装饰器可以对原函数的输入参数进行验证、修改返回值、缓存结果等,从而改变函数的行为。
3. 分离关注点:通过使用装饰器,可以将与函数功能无关的代码(如日志记录、权限验证)与函数本身的实现分离开来,提高代码的可读性和可维护性。
4. 动态创建函数:装饰器可以在运行时动态创建函数,根据不同的条件返回不同的函数,实现动态的函数生成。
需要注意的是,装饰器只是对函数进行修饰,不会改变函数的原始定义。同时,装饰器可以堆叠使用,多个装饰器可以依次修饰同一个函数,形成装饰器链。
1、常规方法装饰功能
给方法执行时,添加额外的功能:输出方法运行的时长。
现在有2个方法:
import time # 导入time模块
def index():
time.sleep(1) # 让程序休眠1秒钟
print('------------这里是index函数------------')
def home():
time.sleep(1) # 让程序休眠1秒钟
print('------------这里是home函数------------')
方法实现:
将目标方法包装到另外一个方法中进行统一处理
mport time # 导入time模块
def index():
time.sleep(1) # 让程序休眠1秒钟
print('------------这里是index函数------------')
def home():
time.sleep(1) # 让程序休眠1秒钟
print('------------这里是home函数------------')
def get_time(function_name):
start_time = time.time() # 获取运行函数前的时间
function_name() # 调用函数
end_time = time.time() # 获取运行结束后的时间
print("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time)) # 输出时间差
print("------------------------------------------------")
get_time(index)
get_time(home)
控制台输出结果:
------------这里是index函数------------
index 执行耗时:1.004432201385498
------------------------------------------------
------------这里是home函数------------
home 执行耗时:1.0124742984771729
------------------------------------------------
2、初级装饰器用法
新建测试文件:
import time # 导入time模块
# 装饰对象
def index():
time.sleep(1) # 让程序休眠1秒钟
print('------------这里是index函数------------')
def home():
time.sleep(1) # 让程序休眠1秒钟
print('------------这里是home函数------------')
# 装饰器
def outer(function_name):
def inner():
# 获取运行函数前的时间
start_time = time.time()
# 调用名为function_name函数
function_name()
# 获取函数运行结束后的时间
end_time = time.time()
# 输出时间差
print("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))
print("------------------------------------------------")
# 返回内部函数名
return inner
# 让index()变成inner()
index = outer(index)
index()
# 让home()变成inner()
home = outer(home)
home()
控制台输出结果:
这个时候函数只需要调用它自己就能输出运行时间了,相当于给函数内部添加了一段代码一样。
------------这里是index函数------------
index 执行耗时:1.0032925605773926
------------------------------------------------
------------这里是home函数------------
home 执行耗时:1.0116212368011475
------------------------------------------------
3、装饰有传参要求的方法
给装饰器加上可变长形参就可以接受任何方法需要的参数。
新建测试文件:
import time # 导入time模块
"""
方法传参:*args, **kwargs
"""
def index(a): # 有参数
time.sleep(1)
print('------------这里是index函数,接受的参数是:{}'.format(a))
def home(): # 无参数
time.sleep(1)
print('------------这里是home函数------------')
# 装饰器
def outer(function_name):
def inner(*args, **kwargs): # 添加可变长形参
start_time = time.time()
function_name(*args, **kwargs) # 传参
end_time = time.time()
# 输出时间差
print("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))
print("------------------------------------------------")
return inner
index = outer(index)
index(555)
home = outer(home)
home()
控制台输出结果:
------------这里是index函数,接受的参数是:555
index 执行耗时:1.0144884586334229
------------------------------------------------
------------这里是home函数------------
home 执行耗时:1.0140643119812012
------------------------------------------------
4、装饰有返回信息的方法
如果函数有返回值,则在装饰器内部函数加一个return返回值就行了。
新建测试文件:
import time # 导入time模块
"""
方法传参:*args, **kwargs
方法return
"""
def index(a): # 有参数
time.sleep(1)
print('------------这里是index函数,接受的参数是:{}'.format(a))
return a * 2 # 有返回值
def home(): # 无参数
time.sleep(1)
print('------------这里是home函数------------')
def outer(function_name):
def inner(*args, **kwargs): # 添加可变长形参
start_time = time.time()
# 将函数返回值赋值给res
res = function_name(*args, **kwargs) # 传参
end_time = time.time()
# 输出时间差
print("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))
print("{} 方法返回信息:{}".format(function_name.__name__, res))
"""返回函数的返回值"""
return res
return inner
index = outer(index)
print(index(555))
home = outer(home)
home()
控制台输出结果:
------------这里是index函数,接受的参数是:555
index 执行耗时:1.0148656368255615
index 方法返回信息:1110
1110
------------这里是home函数------------
home 执行耗时:1.0151972770690918
home 方法返回信息:None
5、装饰器模板&语法糖用法
编写装饰器其实有一套固定的代码 不需要做任何理解
"""编写装饰器其实有一套固定的代码 不需要做任何理解"""
def decorator_func(function_name): # func_name用于接收被装饰的对象(函数)
def inner(*args, **kwargs):
print('执行被装饰函数之前 可以做的额外操作')
res = function_name(*args, **kwargs) # 执行真正的被装饰函数
print('执行被装饰函数之后 可以做的额外操作')
return res # 返回真正函数的返回值
return inner
python给了一个方法,用@+装饰器名称放在装饰对象的上方
新建测试文件:
其中 from api_test_demo._07_log.log_demo_10 import MyLogger 代码文件参考以往的文章《Pytest测试框架中的日志记录功能详解》中第10讲。
"""
装饰器模板&语法糖
"""
import time
from api_test_demo._07_log.log_demo_10 import MyLogger
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_05_{}.log').my_logger()
def decorator_func(function_name): # func_name用于接收被装饰的对象(函数)
def inner(*args, **kwargs):
logger.info('-----------这里是 {} 函数------------'.format(function_name.__name__))
start_time = time.time()
res = function_name(*args, **kwargs) # 执行真正的被装饰函数
end_time = time.time()
logger.info("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))
logger.info("{} 方法返回信息:{}".format(function_name.__name__, res))
return res # 返回真正函数的返回值
return inner
'''
语法糖内部原理:
使用的时候最好紧跟在被装饰对象的上方
语法糖会自动将下面紧挨着的函数名传给@后面的函数调用
'''
@decorator_func
def index(a): # 有参数
time.sleep(1)
return a * 2 # 有返回值
index(888)
控制台输出结果:
文件中记录结果:
6、装饰器修复功能
在Python中,使用@wraps装饰器是为了解决装饰器函数修改原函数属性的问题。
当我们使用装饰器修饰一个函数时,原函数的一些属性(如__name__、__doc__等)会被装饰器函数的属性所替代,这可能会导致一些问题,比如在调试或文档生成时无法正确显示原函数的信息。
@wraps装饰器是functools模块中的一个函数,它提供了一个简单的方式来解决这个问题。它会将装饰器函数的属性复制到原函数上,保持原函数的属性不变。
使用@wraps装饰器的语法如下:
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 装饰器函数的逻辑
pass
return wrapper
新建测试文件(未添加@wraps装饰器):
"""
装饰器修复 wraps
"""
import time
from functools import wraps
from api_test_demo._07_log.log_demo_10 import MyLogger
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_06_{}.log').my_logger()
def decorator_func(function_name): # func_name用于接收被装饰的对象(函数)
def inner(*args, **kwargs):
logger.info('-----------这里是 {} 函数------------'.format(function_name.__name__))
start_time = time.time()
res = function_name(*args, **kwargs) # 执行真正的被装饰函数
end_time = time.time()
logger.info("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))
logger.info("{} 方法返回信息:{}".format(function_name.__name__, res))
return res # 返回真正函数的返回值
return inner
@decorator_func
def index(a): # 有参数
time.sleep(1)
return a * 2 # 有返回值
print(index)
控制台输出结果:
可以看出,这时的index是属于inner中的方法
<function decorator_func.<locals>.inner at 0x000001467F3F70D0>
新建测试文件(添加@wraps装饰器):
"""
装饰器修复 wraps
"""
import time
from functools import wraps
from api_test_demo._07_log.log_demo_10 import MyLogger
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_06_{}.log').my_logger()
def decorator_func(function_name): # func_name用于接收被装饰的对象(函数)
@wraps(function_name) # 修复
def inner(*args, **kwargs):
logger.info('-----------这里是 {} 函数------------'.format(function_name.__name__))
start_time = time.time()
res = function_name(*args, **kwargs) # 执行真正的被装饰函数
end_time = time.time()
logger.info("{} 执行耗时:{}".format(function_name.__name__, end_time - start_time))
logger.info("{} 方法返回信息:{}".format(function_name.__name__, res))
return res # 返回真正函数的返回值
return inner
@decorator_func
def index(a): # 有参数
time.sleep(1)
return a * 2 # 有返回值
print(index)
控制台输出结果:
现在可以看到,index还是原来的index方法
<function index at 0x0000019D8FDF70D0>
7、多个装饰器
新建测试文件:
"""
装饰多个装饰器
"""
import time
from functools import wraps
from api_test_demo._07_log.log_demo_10 import MyLogger
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_07_{}.log').my_logger()
# 装饰器01
def decorator_func_01(function_name):
@wraps(function_name)
def decorator(*args, **kwargs):
logger.info('-----------执行了:decorator_func_01------------')
res = function_name(*args, **kwargs) # 执行真正的被装饰函数
logger.info("{} 方法返回信息:{}".format(function_name.__name__, res * 1))
return res # 返回真正函数的返回值
return decorator
# 装饰器02
def decorator_func_02(function_name):
@wraps(function_name)
def decorator(*args, **kwargs):
logger.info('-----------执行了:decorator_func_02------------')
res = function_name(*args, **kwargs) # 执行真正的被装饰函数
logger.info("{} 方法返回信息:{}".format(function_name.__name__, res * 2))
return res # 返回真正函数的返回值
return decorator
# 装饰器03
def decorator_func_03(function_name):
@wraps(function_name)
def decorator(*args, **kwargs):
logger.info('-----------执行了:decorator_func_03------------')
res = function_name(*args, **kwargs) # 执行真正的被装饰函数
logger.info("{} 方法返回信息:{}".format(function_name.__name__, res * 3))
return res # 返回真正函数的返回值
return decorator
@decorator_func_01
@decorator_func_02
@decorator_func_03
def index(a):
time.sleep(1)
return a
index(999)
控制台输出结果:
当连用多个语法糖的时候,优先执行靠近装饰对象的语法糖,并且直到最远的语法糖才会赋值给装饰对象的函数名。
也就是说,在上述代码中,优先执行@decorator_func_03,将decorator_func_03装饰到index中,但此时不会赋值给index函数名,执行到@decorator_func_01时,这时就会执行赋值语句,也就是index = decorator_func_03(index)。
8、装饰器传参功能
可以通过在装饰器外层再嵌套一个函数进行参数传递。
新建测试文件:
"""
带参数的装饰器
"""
from functools import wraps
from api_test_demo._07_log.log_demo_10 import MyLogger
logger = MyLogger(log_filename_default='./log_dir/decorator_demo_08_{}.log').my_logger()
# 最外层函数,可以接收参数给装饰器使用
def decorator_func_outer(decorator_param):
# 装饰器
def decorator_func(func):
@wraps(func)
def decorator(*args, **kwargs):
logger.info('-----------执行了:decorator_func------------')
if 1 < decorator_param <= 99:
logger.info("do something with function: 1 < decorator_param <= 99")
if 99 < decorator_param <= 999:
logger.info("do something with function: 99 < decorator_param <= 999")
if decorator_param > 999:
logger.info("do something with function: decorator_param>999")
res = func(*args, **kwargs) # 执行真正的被装饰函数
logger.info("{} 方法返回信息:{}".format(func.__name__, res))
return res * 2 # 返回真正函数的返回值
return decorator
# 这里返回装饰器的函数名
return decorator_func
@decorator_func_outer(decorator_param=6)
def index(a):
return a
index(a=888)
控制台输出结果:
总结
Decorator是一种强大的Python语言特性,可以通过装饰器函数对其他函数进行修饰,从而实现功能增强、行为修改、关注点分离等效果。同时,使用@wraps装饰器可以保持原函数的属性不变,提高代码的可维护性和可读性。
题外话
感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。
👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
工具都帮大家整理好了,安装就可直接上手!
三、最新Python学习笔记
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
四、Python视频合集
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
五、实战案例
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、面试宝典
简历模板
👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)
若有侵权,请联系删除