13. 装饰器
- 概述:装饰器是一种在【不修改原函数代码】的前提下,对函数进行【增强】的工具。 它是 Python 中非常强大的语法特性,常用于:日志、校验、计时、缓存、权限控制等。
- 核心语法: 装饰器是一种可调用对象(通常是函数),接收一个函数作为参数,并返回一个新函数。
13.1. 函数装饰器
需求:在不修改add函数的前提下,给add函数增加一些额外的功能,例如:计算前打印一句欢迎语。
1️⃣定义函数装饰器
def say_hello(func):
def wrapper(*args, **kwargs):
print('你好,我要开始计算了')
return func(*args, **kwargs)
return wrapper
定义装饰器核心规则:
- 接收被装饰的函数、同时返回新函数(wrapper)
- 装饰器“吐出来”的是 wrapper 函数,以后别人调用的也是 wrapper 函数。
- 为了保证参数的兼容性,wrapper 函数要通过 *args 和 **kwargs 接收参数。
- wrapper 函数中主要做的是:调用原函数(被装饰的函数)、执行其它逻辑,但要记得将原函数的返回值 return 出去。
2️⃣使用函数装饰器(手动装饰)
def say_hello(func):
def wrapper(*args, **kwargs):
print('你好,我要开始计算了')
return func(*args, **kwargs)
return wrapper
# 调用say_hello装饰器,对add函数进行装饰,并得到装饰后的新函数
add = say_hello(add)
result = add(10, 20, 30)
print(result)
3️⃣使用函数装饰器(语法糖 @)
def say_hello(func):
def wrapper(*args, **kwargs):
print('你好,我要开始计算了')
return func(*args, **kwargs)
return wrapper
@say_hello
def add(x, y, z):
res = x + y + z
print(f'{x}和{y}和{z}相加的结果是:{res}')
return res
result1 = add(10, 20, 30)
print(result1)
上述代码的执行流程:
- @say_hello 会自动执行: add = say_hello(add)。
- 以后调用 add()时,真正执行的是wrapper()。
4️⃣带参数的函数装饰器(三层嵌套)
需求:装饰不同的函数,打印不同的欢迎语。
核心:带参数的装饰器最终是三层嵌套结构:外层接收配置、中间层接收函数、内层接收具体参数。
def say_hello(msg):
def outer(func):
def wrapper(*args, **kwargs):
print(f'你好,我要开始{msg}计算了')
return func(*args, **kwargs)
return wrapper
return outer
# 装饰加法函数
@say_hello('加法')
def add(x, y, z):
res = x + y + z
print(f'{x}和{y}和{z}相加的结果是:{res}')
return res
# 装饰减法函数
@say_hello('减法')
def sub(x, y):
res = x - y
print(f'{x}和{y}相减的结果是:{res}')
return res
# 测试代码
result1 = add(10, 20, 30)
print(result1)
result2 = sub(20, 10)
print(result2)
5️⃣多个函数装饰器一起使用
核心:注意装饰顺序,距离函数最近的装饰器,会先工作。
例如下面代码:test2先装饰,test1再装饰。
def test1(func):
print('我是test1装饰器')
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print('test1追加的逻辑')
return res
return wrapper
def test2(func):
print('我是test2装饰器')
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print('test2追加的逻辑')
return res
return wrapper
@test1
@test2
def add(x, y):
res = x + y
print(f'{x}和{y}相加的结果是{res}')
return res
result = add(10, 20)
print(result)
13.2. 类装饰器
- 包含__call__方法的类,就是类装饰器。
- 像调用函数一样,去调用类装饰器的实例对象,就会触发__call__方法的调用。
- __call__方法通常接收一个函数作为参数,并且会返回一个新函数。
1️⃣定义类装饰器
需求和之前一样,还是给add函数增加【打印欢迎语】的功能。
class SayHello:
def __call__(self, func):
def wrapper(*args, **kwargs):
print('你好,我要开始计算了')
return func(*args, **kwargs)
return wrapper
2️⃣使用类装饰器(手动装饰)
使用类装饰器的流程:先创建类的实例对象,随后调用实例对象,并传入要装饰的函数
class SayHello:
def __call__(self, func):
def wrapper(*args, **kwargs):
print('你好,我要开始计算了')
return func(*args, **kwargs)
return wrapper
# 使用 SayHello 去装饰 add 函数(手动装饰)
say = SayHello()
add = say(add)
# 调用装饰后的函数
result = add(10, 20)
print(result)
3️⃣使用类装饰器(语法糖 @)
通过语法糖@使用装饰器时,类名后要加圆括号调用
class SayHello:
def __call__(self, func):
def wrapper(*args, **kwargs):
print('你好,我要开始计算了')
return func(*args, **kwargs)
return wrapper
@SayHello()
def add(x, y):
res = x + y
print(f'{x}和{y}相加的结果是{res}')
return res
# 依然按照原本的方式调用,但调用的是被装饰后的新函数
result = add(10, 20)
print(result)
4️⃣带参数的类装饰器
带参数的类装饰器写起来,要比带参数的函数装饰器简单,不需要三层嵌套结构
class SayHello:
def __init__(self, msg):
self.msg = msg
def __call__(self, func):
def wrapper(*args, **kwargs):
print(f'你好,我要开始{self.msg}计算了')
return func(*args, **kwargs)
return wrapper
@SayHello('加法')
def add(x, y):
res = x + y
print(f'{x}和{y}相加的结果是{res}')
return res
result = add(10, 20)
print(result)
5️⃣多个类装饰器一起使用
和之前的函数装饰器一样,离函数近的装饰器,先工作。
# 多个类装饰器的使用
class Test1:
def __call__(self, func):
def wrapper(*args, **kwargs):
print('我是Test1追加的逻辑')
return func(*args, **kwargs)
return wrapper
class Test2:
def __call__(self, func):
def wrapper(*args, **kwargs):
print('我是Test2追加的逻辑')
return func(*args, **kwargs)
return wrapper
@Test1()
@Test2()
def add(x, y):
res = x + y
print(f'{x}和{y}相加的结果是{res}')
return res
result = add(10, 20)
print(result)
14. 类型注解 vs 函数类型注解
类型注解不会影响程序运行,它只是给人和工具看的,它可以提高代码可读性、让 IDE 智能提示更强 。
14.1. 变量类型注解
语法格式:变量名: 类型 = 值。
num: int = 100
prcie: float = 12.5
message: str = '你好啊'
is_vip: bool = True
result: None = None # 语法上没有问题,但这么写没有意义
注意:可以先写变量的类型注解,以后再赋值。
school: str
print('*******', school)
school = ''
备注:上述代码中,school: str并不是在定义变量,只是说明:如果未来有school变量,那应该是str类型。Python 执行到school = ”这句代码时,才会真正的定义school变量。
容器类型的注解:
# hobby 是列表,并且列表中的所有元素必须是 str 类型
hobby: list[str] = ['抽烟', '喝酒', '烫头']
# hobby 是列表,并且列表中的元素,可以是:str 或 int 类型
hobby: list[str | int] = ['抽烟', '喝酒', '烫头']
# 上面这行代码的旧写法如下:
from typing import Union
hobby: list[Union[str, int]] = ['抽烟', '喝酒', '烫头']
# citys 是集合,并且集合中所有元素必须是 str 类型
citys: set[str] = {'北京', '上海', '深圳'}
# citys 是集合,并且集合中所有元素可以是:str 或 float 或 bool 类型
citys: set[str | float | bool] = {'北京', '上海', '深圳'}
# persons 是字典,键是 str 类型,值是 int 类型
persons: dict[str, int] = {'张三': 18, '李四': 19, '王五': 20}
# persons 是字典,键是 str 或 int 类型,值是 int 类型
persons: dict[str | int, int] = {'张三': 18, '李四': 19, '王五': 20}
元组的类型注解有些特殊:
# scores 是元组,并且元组中仅包含1个int类型的元素
scores: tuple[int] = (60,)
# scores 是元组,并且元组中包含3个int类型的元素
scores: tuple[int, int, int] = (60, 70, 80)
# scores 是元组,并且元组中包含任意个数的元素,但每个元素的类型必须是int
scores: tuple[int, ...] = (60, 70, 80, 90, 100)
# scores 是元组,并且元组中包含任意个数的元素,每个元素的类型可以是:int 或 str
scores: tuple[int | str, ...] = (60, '70', 80, '90', 100)
Python 中存在类型推导:根据变量初始赋值的实际数据,自动推断变量的类型。
- 对于非容器变量:后续如果改变类型,不会警告。
- 对于容器变量:要求内部元素类型必须与推导出来的一致,否则就会警告。
x = 100
x = ''
y = [10, 20, 30]
y.append('40') # 此行会有警告
14.2. 函数类型注解
- 函数类型注解:给函数的【参数】和【返回值】添加类型说明。
- 语法格式:函数名(参数1: 类型, 参数2: 类型) -> 返回值类型:
1️⃣给参数和返回值加类型注解
def add(x: int, y: int) -> int:
return x + y
2️⃣ 带默认值的参数,可以不写注解
def add(x=1, y=1) -> int:
return x + y
3️⃣ 设置多个返回值的类型注解
def show_nums_info(nums: list[int]) -> tuple[int, int, float]:
max_v = max(nums)
min_v = min(nums)
return max_v, min_v, max_v / min_v
4️⃣ 可变参数类型注解
# 设置 *args 的类型注解,要求 args 中的每个参数都必须是 int 类型
def add(*args: int) -> int:
return sum(args)
# 设置 **kwargs 的类型注解,要求 kwargs 中的每组参数的值,必须是 str 或 int 类型
def show_info(**kwargs: str | int):
print(kwargs)
5️⃣获取函数的注解信息
print(add.__annotations__)

AI项目博客






