目标
熟悉装饰器的作用和基本使用方法,熟悉Python的一些常见的装饰器方法。
Python版本
Python 3.9.18
官方文档
装饰器https://docs.python.org/3.9/glossary.html#term-decorator
概述
装饰器
官方文档
A function returning another function, usually applied as a function transformation using the
@wrapper
syntax. Common examples for decorators are classmethod() and staticmethod().
decorators ::= decorator+
decorator ::= "@" assignment_expression NEWLINE
理解
- 装饰器本质上是一个接受函数作为参数并返回一个新函数的函数,这个新函数通常在原函数执行前后添加额外的行为。
- decorators ::= decorator+表示一个函数或方法可以有多个装饰器。
- decorator ::= "@" assignment_expression NEWLINE描述了单个装饰器的结构,它由一个@符号引入,后面跟着一个赋值表达式(装饰器函数),然后是换行。
语法糖
语法糖本质是通过更加简洁、易读的语法来表达原本可以用更复杂或冗长的方式实现的功能。比如JDK8的新特性就有Lambda表达式,它简化了复杂的代码,可以理解为语法糖。
自定义装饰器
没有参数传递
需求
做一个性能监控的装饰器,用于计算方法或函数的耗时。
import time
def calculation_time(fun):
def wrapper(*args, **kwargs):
print(f"传如方法的id:{id(fun)}")
print(f"wrapper方法的id:{id(wrapper)}")
#开始时间
start_time = time.time()
result = fun(*args, **kwargs)
end_time = time.time()
print(f"方法或函数名称:{fun.__name__};运行耗时:{end_time-start_time}秒。")
print(f"这是方法或函数的返回值:{result},如果被修饰的方法或函数没有返回值,则可以省略,但为了代码的健壮性我们往往会把返回值加上。")
return result
return wrapper
@calculation_time
def fun():
print(f"fun方法的id:{id(fun)}")
time.sleep(2)
print("hello world!")
if __name__ == '__main__':
fun()
输出结果
传如方法的id:2059984063360
wrapper方法的id:2059984066560
fun方法的id:2059984066560
hello world!
方法或函数名称:fun;运行耗时:2.0003597736358643秒。
这是方法或函数的返回值:None,如果被修饰的方法或函数没有返回值,则可以省略,但为了代码的健壮性我们往往会把返回值加上。
总结
代码中定义的装饰器有两个函数:
- 第一个函数:返回一个包装过的函数wrapper,实际上就是替换了fun方法,这个结论从输出的id值来看得到了验证。
- 第二个函数:对原函数或方法的增强。如果原函数或方法没有返回值,我们可以省略第二个函数的返回值。但是为了代码的健壮性,我们往往会加上返回值。
- 通过注解调用装饰器是语法糖,我们还可以手动调用装饰器,这在语义上是等价的。
if __name__ == '__main__':
fun = calculation_time(fun)
fun()
有参数传递和返回值
需求
创建一个函数,用于处理学生基本信息。创建装饰器,在函数被调用前做参数验证。
def validate_student_info(func):
def wrapper(*args, **kwargs):
# 参数验证
name, age, sex, scores, hobbies = args
# 验证 name
if not isinstance(name, str) or not name.strip():
raise ValueError("姓名必须是非空字符串")
# 验证 age
if not isinstance(age, int) or age <= 0:
raise ValueError("年龄必须是正整数")
# 验证 sex
if sex not in ["男", "女"]:
raise ValueError("性别必须是 '男' 或 '女'")
# 验证 scores
if not isinstance(scores, dict):
raise ValueError("分数必须是字典")
for subject, score in scores.items():
if not isinstance(score, int):
raise ValueError(f"科目 '{subject}' 的分数必须是整数")
# 验证 hobbies
if not isinstance(hobbies, list):
raise ValueError("爱好必须是列表")
for hobby in hobbies:
if not isinstance(hobby, str):
raise ValueError(f"爱好 '{hobby}' 必须是字符串")
# 如果验证通过,调用原始函数
return func(*args, **kwargs)
return wrapper
@validate_student_info
def add_student_info(name, age, sex, scores, hobbies):
"""
:param name: 姓名
:param age: 年龄
:param sex: 性别
:param scores: 多个科目的考试分数 dict类型
:param hobbies: 多个爱好 list类型
:return: 结果
"""
print("完成学生信息入库。")
return "SUCCESS"
if __name__ == '__main__':
# 正确的参数
try:
result = add_student_info("小李", 20, "女", {"Math": 90, "English": 85}, ["乒乓球", "音乐"])
print(result)
except ValueError as e:
print(f"验证失败: {e}")
# 错误的参数
try:
result = add_student_info("小张", 5, "男2", {"Math": 90, "English": 85}, ["排球", "篮球"])
print(result)
except ValueError as e:
print(f"验证失败: {e}")
总结
手动调用装饰器与上个案例一样:
if __name__ == '__main__':
# 手动调用装饰器
add_student_info = validate_student_info(add_student_info)
# 正确的参数
try:
result = add_student_info("小李", 20, "女", {"Math": 90, "English": 85}, ["乒乓球", "音乐"])
print(result)
except ValueError as e:
print(f"验证失败: {e}")
# 错误的参数
try:
result = add_student_info("小张", 5, "男2", {"Math": 90, "English": 85}, ["排球", "篮球"])
print(result)
except ValueError as e:
print(f"验证失败: {e}")