AI项目博客
记录 AI 工程实践与大模型应用

Python 学习笔记 22|函数进阶 · 装饰器

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__)
赞(0)
未经允许不得转载:AI项目博客 » Python 学习笔记 22|函数进阶 · 装饰器

评论 抢沙发

AI项目博客

专注 AI 工程化落地 —— 覆盖智能体、大模型、Java AI 与 Python 的学习笔记,以及 AI 平台的项目实战记录。

关于本站文章归档

登录

找回密码

注册