Python 装饰器从入门到精通:通过实战案例带你吃透 AOP 编程思想

目录

 

​编辑

引言:为什么装饰器是 Python 开发者的 "效率神器"?

一、基础铺垫:理解装饰器的 "前世今生"

1.1 函数是一等公民:装饰器的基石

示例:函数的 "一等公民" 特性

1.2 装饰器的本质:一个 "函数包装器"

示例:最简单的装饰器

1.3 语法糖:@符号的便捷用法

示例:使用@符号简化装饰器调用

二、装饰器进阶:处理复杂场景

2.1 处理带参数的被装饰函数

示例:支持任意参数的装饰器

2.2 带参数的装饰器

示例:带参数的日志装饰器

2.3 保留被装饰函数的元信息

问题示例:元信息被覆盖

解决方案:使用functools.wraps

2.4 装饰器的嵌套使用

示例:装饰器嵌套

三、类装饰器:另一种实现方式

3.1 基础类装饰器

示例:基础类装饰器

3.2 带参数的类装饰器

3.3 函数装饰器 vs 类装饰器:如何选择?

四、实战场景:装饰器的 10 个经典应用

4.1 场景 1:日志记录装饰器

4.2 场景 2:性能计时装饰器

4.3 场景 3:缓存装饰器

4.4 场景 4:权限验证装饰器

4.5 场景 5:参数校验装饰器

4.6 场景 6:异常捕获装饰器

4.7 场景 7:重试装饰器

4.8 场景 8:事务管理装饰器

4.9 场景 9:限流装饰器

4.10 场景 10:异步函数装饰器

五、避坑指南:装饰器开发的 10 个常见错误

5.1 错误 1:忘记保留被装饰函数的元信息

5.2 错误 2:被装饰函数有返回值但未处理

5.3 错误 3:带参数的装饰器实现层级错误

5.4 错误 4:装饰器嵌套执行顺序理解错误

5.5 错误 5:异步函数使用同步装饰器

5.6 错误 6:缓存装饰器用于不可哈希参数

5.7 错误 7:装饰器导致函数递归调用自身

5.8 错误 8:权限验证装饰器未处理用户上下文

5.9 错误 9:重试装饰器未指定异常类型

5.10 错误 10:装饰器的副作用未清理

六、实战项目:用装饰器打造一个简易接口框架

6.1 项目结构

6.2 完整代码实现

6.3 运行结果

6.4 项目亮点

七、总结与进阶学习

7.1 装饰器核心要点总结

7.2 进阶学习方向

7.3 学习建议


 

class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

引言:为什么装饰器是 Python 开发者的 "效率神器"?

在 Python 开发中,你是否遇到过这样的场景:

  • 想给多个函数添加日志记录功能,却不想在每个函数里重复写日志代码;
  • 想给接口添加权限验证,却不想修改接口的核心逻辑;
  • 想统计函数的执行时间,却不想侵入函数内部实现。

这时候,装饰器就能派上大用场!它作为 Python 的核心语法糖,本质是 "函数包装器",能在不修改原函数代码、不改变函数调用方式的前提下,为函数添加额外功能。这种 "无侵入式增强" 的特性,完美契合了AOP(面向切面编程) 思想,让代码更简洁、更易维护。

装饰器的应用场景远不止这些:缓存控制、异常捕获、参数校验、事务管理等场景都能看到它的身影。无论是开发基础工具还是大型框架(如 Django、Flask 中的@route@login_required),装饰器都是提升开发效率的关键工具。

本文将从 "零基础理解" 到 "高级实战",用通俗的语言 + 可运行的示例,系统拆解 Python 装饰器的核心原理与应用技巧。全程无晦涩概念堆砌,让你从 "会用" 到 "吃透",真正掌握这个 Python 开发者必备的核心技能。

一、基础铺垫:理解装饰器的 "前世今生"

在学习装饰器之前,必须先掌握 Python 的一个核心概念:函数是一等公民。这是装饰器实现的基础,也是理解其原理的关键。

1.1 函数是一等公民:装饰器的基石

Python 中的函数具备以下特性,这让它能像普通变量一样被操作:

  1. 函数可以赋值给变量;
  2. 函数可以作为参数传递给其他函数;
  3. 函数可以作为其他函数的返回值;
  4. 函数可以在函数内部定义(嵌套函数)。

示例:函数的 "一等公民" 特性

# 1. 函数可以赋值给变量
def greet(name):
    return f"Hello, {name}!"

hello = greet  # 将函数赋值给变量
print(hello("Python"))  # 输出:Hello, Python!

# 2. 函数可以作为参数传递
def apply_func(func, arg):
    return func(arg)

result = apply_func(greet, "Decorator")
print(result)  # 输出:Hello, Decorator!

# 3. 函数可以作为返回值
def create_greeter():
    def inner_greet(name):
        return f"Hi, {name}!"
    return inner_greet  # 返回嵌套函数

greeter = create_greeter()
print(greeter("World"))  # 输出:Hi, World!

# 4. 函数内部可以定义函数(嵌套函数)
def outer():
    def inner():
        return "I'm inner function!"
    return inner()

print(outer())  # 输出:I'm inner function!

正是这些特性,让 Python 能够实现 "用函数包装函数" 的装饰器模式。

1.2 装饰器的本质:一个 "函数包装器"

装饰器本质上是一个接收函数作为参数,并返回新函数的函数。它的核心逻辑是:

  1. 接收一个待增强的函数(被装饰函数);
  2. 定义一个新函数,在新函数中调用被装饰函数,并添加额外功能;
  3. 返回这个新函数,替代原函数。

示例:最简单的装饰器

# 定义装饰器(接收函数作为参数)
def my_decorator(func):
    # 定义嵌套函数(包装函数)
    def wrapper():
        print("函数执行前:添加额外功能")
        func()  # 调用被装饰函数
        print("函数执行后:添加额外功能")
    # 返回包装函数
    return wrapper

# 定义被装饰函数
def say_hello():
    print("Hello, Decorator!")

# 使用装饰器:用包装函数替代原函数
say_hello = my_decorator(say_hello)

# 调用函数(实际调用的是wrapper)
say_hello()

运行结果:

函数执行前:添加额外功能
Hello, Decorator!
函数执行后:添加额外功能

可以看到,我们没有修改say_hello函数的任何代码,却为它添加了 "执行前 / 后打印日志" 的功能。这就是装饰器的核心价值:无侵入式增强函数功能

1.3 语法糖:@符号的便捷用法

上面的示例中,say_hello = my_decorator(say_hello)的写法略显繁琐。Python 提供了@符号作为装饰器的语法糖,让代码更简洁:

示例:使用@符号简化装饰器调用

def my_decorator(func):
    def wrapper():
        print("函数执行前:添加额外功能")
        func()
        print("函数执行后:添加额外功能")
    return wrapper

# 使用@符号应用装饰器(等价于 say_hello = my_decorator(say_hello))
@my_decorator
def say_hello():
    print("Hello, Decorator!")

# 直接调用函数即可
say_hello()

运行结果与之前完全一致。@my_decorator就像给say_hello函数 "穿上了一件新衣服",这件衣服就是装饰器提供的额外功能。

二、装饰器进阶:处理复杂场景

基础装饰器只能处理无参数、无返回值的简单函数。实际开发中,函数往往有参数、有返回值,甚至装饰器本身也需要参数。本节将拆解这些复杂场景的实现方式。

2.1 处理带参数的被装饰函数

如果被装饰函数有参数,直接调用func()会报错。解决思路是:让包装函数wrapper接收任意参数,并传递给func

示例:支持任意参数的装饰器

def log_decorator(func):
    # 使用*args接收位置参数,**kwargs接收关键字参数
    def wrapper(*args, **kwargs):
        print(f"调用函数:{func.__name__}")
        print(f"位置参数:{args}")
        print(f"关键字参数:{kwargs}")
        # 将参数传递给被装饰函数
        result = func(*args, **kwargs)
        print(f"函数返回值:{result}")
        return result  # 返回被装饰函数的结果
    return wrapper

# 带参数的被装饰函数
@log_decorator
def add(a, b):
    return a + b

@log_decorator
def greet(name, message="Hello"):
    return f"{message}, {name}!"

# 调用函数
add(10, 20)
print("-" * 20)
greet("Python", message="Hi")

运行结果:

调用函数:add
位置参数:(10, 20)
关键字参数:{}
函数返回值:30
--------------------
调用函数:greet
位置参数:('Python',)
关键字参数:{'message': 'Hi'}
函数返回值:Hi, Python!

通过*args**kwargs,装饰器可以适配任意参数的函数,通用性大大提升。

2.2 带参数的装饰器

有时装饰器本身需要参数来控制其行为(比如日志装饰器需要指定日志级别)。这种情况下,需要在装饰器外层再套一层 "参数接收函数"。

示例:带参数的日志装饰器

# 外层函数:接收装饰器参数
def log_decorator(level="info"):
    # 中层函数:接收被装饰函数
    def decorator(func):
        # 内层函数:包装逻辑
        def wrapper(*args, **kwargs):
            print(f"[{level.upper()}] 调用函数:{func.__name__}")
            result = func(*args, **kwargs)
            print(f"[{level.upper()}] 函数执行完成")
            return result
        return wrapper
    return decorator

# 使用装饰器并传递参数
@log_decorator(level="debug")
def add(a, b):
    return a + b

@log_decorator(level="warning")
def delete(id):
    print(f"删除ID为{id}的数据")

# 调用函数
add(10, 20)
print("-" * 20)
delete(1001)

运行结果:

[DEBUG] 调用函数:add
[DEBUG] 函数执行完成
--------------------
[WARNING] 调用函数:delete
删除ID为1001的数据
[WARNING] 函数执行完成

带参数的装饰器执行流程:

  1. 执行log_decorator(level="debug"),返回decorator函数;
  2. 执行decorator(add),返回wrapper函数;
  3. add函数被替换为wrapper,调用add(10,20)实际执行wrapper(10,20)

2.3 保留被装饰函数的元信息

使用装饰器后,被装饰函数的元信息(如函数名、文档字符串)会被包装函数wrapper覆盖,这可能导致一些问题(比如调试时无法获取正确的函数名)。

问题示例:元信息被覆盖

def my_decorator(func):
    def wrapper(*args, **kwargs):
        """这是包装函数"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def add(a, b):
    """这是加法函数,返回a+b的结果"""
    return a + b

# 查看函数名和文档字符串
print(add.__name__)  # 输出:wrapper(预期是add)
print(add.__doc__)   # 输出:这是包装函数(预期是加法函数的文档)

解决方案:使用functools.wraps

functools.wraps是 Python 提供的工具,用于将被装饰函数的元信息复制到包装函数中:

import functools

def my_decorator(func):
    # 使用functools.wraps装饰wrapper,保留func的元信息
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        """这是包装函数"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def add(a, b):
    """这是加法函数,返回a+b的结果"""
    return a + b

# 再次查看元信息
print(add.__name__)  # 输出:add(正确)
print(add.__doc__)   # 输出:这是加法函数,返回a+b的结果(正确)

最佳实践:所有装饰器的包装函数都应使用@functools.wraps(func),避免元信息丢失。

2.4 装饰器的嵌套使用

一个函数可以同时应用多个装饰器,执行顺序遵循 "就近原则":靠近函数的装饰器先执行,远离函数的装饰器后执行。

示例:装饰器嵌套

import functools

# 装饰器1:日志记录
def log_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[日志] 调用函数:{func.__name__}")
        result = func(*args, **kwargs)
        print(f"[日志] 函数执行完成")
        return result
    return wrapper

# 装饰器2:性能计时
def time_decorator(func):
    import time
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"[计时] 函数执行耗时:{end_time - start_time:.4f}秒")
        return result
    return wrapper

# 嵌套应用装饰器:先执行log_decorator,再执行time_decorator
@time_decorator
@log_decorator
def slow_function():
    """模拟耗时操作"""
    import time
    time.sleep(1)
    print("耗时操作执行完成")

# 调用函数
slow_function()

运行结果:

[日志] 调用函数:slow_function
耗时操作执行完成
[日志] 函数执行完成
[计时] 函数执行耗时:1.0012秒

执行顺序解析:

  1. @time_decorator@log_decorator嵌套,等价于slow_function = time_decorator(log_decorator(slow_function))
  2. 调用slow_function()时,先执行time_decoratorwrapper
  3. time_decoratorwrapper中调用func,实际是log_decoratorwrapper
  4. log_decoratorwrapper中调用原始的slow_function
  5. 执行完成后,依次返回并执行后续逻辑。

三、类装饰器:另一种实现方式

除了函数装饰器,Python 还支持类装饰器。类装饰器通过重写__call__方法实现,当被装饰函数被调用时,会触发__call__方法的执行。

3.1 基础类装饰器

类装饰器的核心是:将被装饰函数作为参数传入类的构造函数,并重写__call__方法实现包装逻辑。

示例:基础类装饰器

import functools

class LogDecorator:
    # 构造函数:接收被装饰函数
    def __init__(self, func):
        self.func = func
        # 保留被装饰函数的元信息
        functools.update_wrapper(self, func)
    
    # 重写__call__方法:实现包装逻辑
    def __call__(self, *args, **kwargs):
        print(f"[类装饰器] 调用函数:{self.func.__name__}")
        result = self.func(*args, **kwargs)
        print(f"[类装饰器] 函数执行完成")
        return result

# 使用类装饰器
@LogDecorator
def add(a, b):
    """这是加法函数"""
    return a + b

# 调用函数(触发__call__方法)
print(add(10, 20))
print(add.__name__)  # 输出:add(元信息保留)

运行结果:

[类装饰器] 调用函数:add
[类装饰器] 函数执行完成
30
add

3.2 带参数的类装饰器

如果类装饰器需要接收参数,需要在构造函数外层再套一层逻辑(类似函数装饰器的参数传递):

import functools

class LogDecorator:
    # 外层函数:接收装饰器参数
    def __init__(self, level="info"):
        self.level = level
    
    # 重写__call__方法:接收被装饰函数
    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{self.level.upper()}] 调用函数:{func.__name__}")
            result = func(*args, **kwargs)
            print(f"[{self.level.upper()}] 函数执行完成")
            return result
        return wrapper

# 使用带参数的类装饰器
@LogDecorator(level="debug")
def multiply(a, b):
    return a * b

# 调用函数
print(multiply(5, 6))

运行结果:

[DEBUG] 调用函数:multiply
[DEBUG] 函数执行完成
30

3.3 函数装饰器 vs 类装饰器:如何选择?

对比维度函数装饰器类装饰器
实现复杂度简单(函数嵌套)中等(类 +__call__方法)
状态保持需借助闭包或全局变量天然支持(实例属性)
灵活性高(易修改包装逻辑)中等(需遵循类的设计规范)
可读性适合简单逻辑(代码简洁)适合复杂逻辑(结构清晰)
适用场景简单功能增强(日志、计时)复杂功能(状态管理、缓存)

选择建议

  • 简单场景(如日志、计时)优先用函数装饰器,代码更简洁;
  • 复杂场景(如需要维护状态的缓存装饰器)用类装饰器,结构更清晰。

四、实战场景:装饰器的 10 个经典应用

装饰器的价值在于解决实际问题。本节将结合企业开发中的高频场景,提供 10 个可直接复用的装饰器实战案例。

4.1 场景 1:日志记录装饰器

为函数添加详细的日志记录,包括调用参数、返回值、执行时间,便于调试和问题排查。

import functools
import logging
import time

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

def log_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 记录调用信息
        logging.info(f"函数 {func.__name__} 开始执行")
        logging.info(f"位置参数:{args}")
        logging.info(f"关键字参数:{kwargs}")
        
        start_time = time.time()
        try:
            # 执行函数并获取返回值
            result = func(*args, **kwargs)
            logging.info(f"函数 {func.__name__} 执行成功,返回值:{result}")
            return result
        except Exception as e:
            # 记录异常信息
            logging.error(f"函数 {func.__name__} 执行失败,异常:{str(e)}", exc_info=True)
            raise  # 重新抛出异常,不影响原函数的异常处理
        finally:
            # 记录执行时间
            end_time = time.time()
            logging.info(f"函数 {func.__name__} 执行耗时:{end_time - start_time:.4f}秒")
    return wrapper

# 测试
@log_decorator
def divide(a, b):
    return a / b

divide(10, 2)
divide(10, 0)  # 触发异常

运行结果:

2024-05-20 10:00:00,000 - INFO - 函数 divide 开始执行
2024-05-20 10:00:00,000 - INFO - 位置参数:(10, 2)
2024-05-20 10:00:00,000 - INFO - 关键字参数:{}
2024-05-20 10:00:00,000 - INFO - 函数 divide 执行成功,返回值:5.0
2024-05-20 10:00:00,000 - INFO - 函数 divide 执行耗时:0.0000秒
2024-05-20 10:00:00,000 - INFO - 函数 divide 开始执行
2024-05-20 10:00:00,000 - INFO - 位置参数:(10, 0)
2024-05-20 10:00:00,000 - INFO - 关键字参数:{}
2024-05-20 10:00:00,000 - ERROR - 函数 divide 执行失败,异常:division by zero
Traceback (most recent call last):
  File "...", line 22, in wrapper
    result = func(*args, **kwargs)
  File "...", line 36, in divide
    return a / b
ZeroDivisionError: division by zero
2024-05-20 10:00:00,000 - INFO - 函数 divide 执行耗时:0.0000秒

4.2 场景 2:性能计时装饰器

统计函数的执行时间,用于性能瓶颈分析。

import functools
import time
import logging

logging.basicConfig(level=logging.INFO)

def time_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()  # 高精度计时
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        elapsed = end_time - start_time
        # 按执行时间分级记录日志
        if elapsed < 0.1:
            logging.info(f"函数 {func.__name__} 执行耗时:{elapsed:.6f}秒(快速)")
        elif elapsed < 1:
            logging.info(f"函数 {func.__name__} 执行耗时:{elapsed:.4f}秒(正常)")
        else:
            logging.warning(f"函数 {func.__name__} 执行耗时:{elapsed:.2f}秒(缓慢)")
        return result
    return wrapper

# 测试
@time_decorator
def fast_function():
    time.sleep(0.05)

@time_decorator
def slow_function():
    time.sleep(1.5)

fast_function()
slow_function()

运行结果:

INFO:root:函数 fast_function 执行耗时:0.050123秒(快速)
WARNING:root:函数 slow_function 执行耗时:1.5023秒(缓慢)

4.3 场景 3:缓存装饰器

缓存函数的执行结果,避免重复计算(适用于计算密集型、参数重复的函数)。

import functools
import time

def cache_decorator(func):
    # 用字典缓存结果:key为参数元组,value为返回值
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 将参数转换为可哈希的元组作为key(kwargs按排序转换为元组)
        key = args + tuple(sorted(kwargs.items()))
        if key in cache:
            print(f"[缓存命中] 函数 {func.__name__} 直接返回缓存结果")
            return cache[key]
        # 缓存未命中,执行函数并缓存结果
        result = func(*args, **kwargs)
        cache[key] = result
        print(f"[缓存未命中] 函数 {func.__name__} 执行并缓存结果")
        return result
    return wrapper

# 测试:计算斐波那契数列(递归方式,存在大量重复计算)
@cache_decorator
def fibonacci(n):
    if n <= 1:
        return n
    time.sleep(0.1)  # 模拟计算耗时
    return fibonacci(n-1) + fibonacci(n-2)

# 第一次调用:缓存未命中
print(fibonacci(5))
print("-" * 20)
# 第二次调用:缓存命中
print(fibonacci(5))

运行结果:

[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
5
--------------------
[缓存命中] 函数 fibonacci 直接返回缓存结果
5

进阶:Python 内置的functools.lru_cache是更强大的缓存装饰器,支持缓存大小限制、过期时间等功能:

import functools
import time

# maxsize:缓存最大数量,None表示无限制;typed:是否区分参数类型
@functools.lru_cache(maxsize=None, typed=True)
def fibonacci(n):
    if n <= 1:
        return n
    time.sleep(0.1)
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(5))
print(fibonacci.cache_info())  # 查看缓存信息

4.4 场景 4:权限验证装饰器

在接口或函数执行前验证用户权限,适用于 Web 应用、后台系统等需要权限控制的场景。

import functools

# 模拟用户信息(实际开发中从数据库/会话获取)
current_user = {
    "username": "admin",
    "role": "admin"  # 角色:admin/editor/guest
}

def permission_required(required_role):
    """权限验证装饰器:需要指定所需角色"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 验证当前用户角色
            if current_user["role"] != required_role:
                raise PermissionError(f"权限不足!需要 {required_role} 角色,当前角色:{current_user['role']}")
            print(f"权限验证通过!当前用户:{current_user['username']}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 测试:不同角色的函数
@permission_required("admin")
def delete_all_data():
    print("删除所有数据(仅管理员可执行)")

@permission_required("editor")
def edit_data(id):
    print(f"编辑ID为{id}的数据(编辑者可执行)")

# 管理员角色测试
delete_all_data()
edit_data(1001)

# 切换用户角色为guest
current_user["role"] = "guest"
try:
    delete_all_data()
except PermissionError as e:
    print(e)

运行结果:

权限验证通过!当前用户:admin
删除所有数据(仅管理员可执行)
权限验证通过!当前用户:admin
编辑ID为1001的数据(编辑者可执行)
权限不足!需要 admin 角色,当前角色:guest

4.5 场景 5:参数校验装饰器

验证函数参数的类型、范围等合法性,避免无效参数导致的错误。

import functools
from typing import Dict, List, Type

def validate_params(param_rules: Dict[str, Type]):
    """参数校验装饰器:验证参数类型是否符合规则"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 结合函数签名获取参数名(处理位置参数)
            import inspect
            sig = inspect.signature(func)
            params = list(sig.parameters.keys())
            
            # 验证位置参数
            for i, (param_name, param_type) in enumerate(param_rules.items()):
                if i >= len(args):
                    break  # 无该位置参数,跳过
                arg_value = args[i]
                if not isinstance(arg_value, param_type):
                    raise TypeError(f"参数 {param_name} 类型错误!预期 {param_type},实际 {type(arg_value)}")
            
            # 验证关键字参数
            for param_name, param_type in param_rules.items():
                if param_name in kwargs:
                    arg_value = kwargs[param_name]
                    if not isinstance(arg_value, param_type):
                        raise TypeError(f"参数 {param_name} 类型错误!预期 {param_type},实际 {type(arg_value)}")
            
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 测试:参数类型校验
@validate_params({"a": int, "b": int})
def add(a, b):
    return a + b

@validate_params({"name": str, "age": int})
def user_info(name, age):
    return f"姓名:{name},年龄:{age}"

# 正确调用
print(add(10, 20))
print(user_info("Python", 30))

# 错误调用(参数类型不匹配)
try:
    add(10, "20")
except TypeError as e:
    print(e)

try:
    user_info("Python", "30")
except TypeError as e:
    print(e)

运行结果:

30
姓名:Python,年龄:30
参数 b 类型错误!预期 <class 'int'>,实际 <class 'str'>
参数 age 类型错误!预期 <class 'int'>,实际 <class 'str'>

4.6 场景 6:异常捕获装饰器

统一捕获函数执行过程中的异常,避免程序崩溃,并进行友好处理。

import functools
import logging

logging.basicConfig(level=logging.ERROR)

def exception_handler(default_return=None, log_exception=True):
    """异常捕获装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                # 记录异常日志
                if log_exception:
                    logging.error(f"函数 {func.__name__} 执行异常:{str(e)}", exc_info=True)
                # 返回默认值(避免程序崩溃)
                return default_return
        return wrapper
    return decorator

# 测试:文件读取函数(可能抛出文件不存在异常)
@exception_handler(default_return="", log_exception=True)
def read_file(file_path):
    with open(file_path, "r", encoding="utf-8") as f:
        return f.read()

# 读取不存在的文件
content = read_file("nonexistent.txt")
print(f"文件内容:{content}")  # 输出:文件内容:(默认返回空字符串)

运行结果:

ERROR:root:函数 read_file 执行异常:[Errno 2] No such file or directory: 'nonexistent.txt'
Traceback (most recent call last):
  File "...", line 14, in wrapper
    return func(*args, **kwargs)
  File "...", line 26, in read_file
    with open(file_path, "r", encoding="utf-8") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'nonexistent.txt'
文件内容:

4.7 场景 7:重试装饰器

当函数执行失败(如网络请求超时、数据库连接失败)时,自动重试指定次数。

import functools
import time
import logging

logging.basicConfig(level=logging.INFO)

def retry(max_attempts=3, delay=1, exceptions=(Exception,)):
    """重试装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    attempts += 1
                    if attempts >= max_attempts:
                        logging.error(f"函数 {func.__name__} 重试 {max_attempts} 次失败,异常:{str(e)}")
                        raise  # 重试次数耗尽,抛出异常
                    logging.warning(f"函数 {func.__name__} 执行失败,将在 {delay} 秒后重试(第 {attempts} 次)")
                    time.sleep(delay)
        return wrapper
    return decorator

# 测试:模拟网络请求(随机失败)
import random

@retry(max_attempts=3, delay=2, exceptions=(ConnectionError,))
def fetch_data(url):
    print(f"尝试请求 URL:{url}")
    # 模拟50%概率失败
    if random.random() < 0.5:
        raise ConnectionError("网络连接超时")
    return f"成功获取 {url} 的数据"

# 调用函数
print(fetch_data("https://api.example.com/data"))

运行结果(可能出现的情况):

尝试请求 URL:https://api.example.com/data
WARNING:root:函数 fetch_data 执行失败,将在 2 秒后重试(第 1 次)
尝试请求 URL:https://api.example.com/data
WARNING:root:函数 fetch_data 执行失败,将在 2 秒后重试(第 2 次)
尝试请求 URL:https://api.example.com/data
成功获取 https://api.example.com/data 的数据

4.8 场景 8:事务管理装饰器

在数据库操作中,确保一系列操作要么全部成功(提交事务),要么全部失败(回滚事务)。

import functools

# 模拟数据库连接和事务
class Database:
    def __init__(self):
        self.connected = True
    
    def begin_transaction(self):
        print("开始事务")
    
    def commit(self):
        print("提交事务")
    
    def rollback(self):
        print("回滚事务")

# 全局数据库实例
db = Database()

def transaction_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            db.begin_transaction()
            result = func(*args, **kwargs)
            db.commit()
            print("事务执行成功")
            return result
        except Exception as e:
            db.rollback()
            print(f"事务执行失败,已回滚:{str(e)}")
            raise
    return wrapper

# 测试:数据库操作
@transaction_decorator
def transfer_money(from_user, to_user, amount):
    print(f"从 {from_user} 扣除 {amount} 元")
    print(f"向 {to_user} 添加 {amount} 元")
    # 模拟异常(比如余额不足)
    if amount > 1000:
        raise ValueError("转账金额超过限额")

# 正常转账
transfer_money("张三", "李四", 500)
print("-" * 20)
# 异常转账(触发回滚)
try:
    transfer_money("张三", "李四", 2000)
except ValueError as e:
    pass

运行结果:

开始事务
从 张三 扣除 500 元
向 李四 添加 500 元
提交事务
事务执行成功
--------------------
开始事务
从 张三 扣除 2000 元
向 李四 添加 2000 元
回滚事务
事务执行失败,已回滚:转账金额超过限额

4.9 场景 9:限流装饰器

限制函数在单位时间内的调用次数,避免高并发场景下的系统过载(适用于接口限流)。

import functools
import time
from collections import defaultdict

# 存储函数调用时间记录:{func_name: [call_times]}
call_records = defaultdict(list)

def rate_limit(limit=10, period=1):
    """限流装饰器:period秒内最多调用limit次"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            now = time.time()
            func_name = func.__name__
            
            # 清理过期的调用记录
            call_records[func_name] = [t for t in call_records[func_name] if now - t < period]
            
            # 检查调用次数是否超过限制
            if len(call_records[func_name]) >= limit:
                raise TooManyRequestsError(f"函数 {func_name} 调用过于频繁!{period}秒内最多调用{limit}次")
            
            # 记录当前调用时间
            call_records[func_name].append(now)
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 自定义异常:调用过于频繁
class TooManyRequestsError(Exception):
    pass

# 测试:限流控制
@rate_limit(limit=3, period=5)  # 5秒内最多调用3次
def query_data():
    print("查询数据成功")

# 连续调用4次
for i in range(4):
    try:
        query_data()
        print(f"第 {i+1} 次调用成功")
    except TooManyRequestsError as e:
        print(f"第 {i+1} 次调用失败:{e}")
    time.sleep(1)

运行结果:

查询数据成功
第 1 次调用成功
查询数据成功
第 2 次调用成功
查询数据成功
第 3 次调用成功
第 4 次调用失败:函数 query_data 调用过于频繁!5秒内最多调用3次

4.10 场景 10:异步函数装饰器

Python 3.5 + 支持异步函数(async def),装饰器需要适配异步逻辑。

import functools
import asyncio
import time

def async_log_decorator(func):
    # 验证被装饰函数是否为异步函数
    if not asyncio.iscoroutinefunction(func):
        raise TypeError("被装饰函数必须是异步函数(async def)")
    
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        print(f"[异步日志] 函数 {func.__name__} 开始执行")
        start_time = time.time()
        result = await func(*args, **kwargs)  # 等待异步函数执行
        end_time = time.time()
        print(f"[异步日志] 函数 {func.__name__} 执行完成,耗时:{end_time - start_time:.4f}秒")
        return result
    return wrapper

# 测试:异步函数
@async_log_decorator
async def async_task():
    """模拟异步耗时操作"""
    await asyncio.sleep(1)
    print("异步任务执行完成")
    return "异步任务结果"

# 运行异步函数
asyncio.run(async_task())

运行结果:

[异步日志] 函数 async_task 开始执行
异步任务执行完成
[异步日志] 函数 async_task 执行完成,耗时:1.0012秒

五、避坑指南:装饰器开发的 10 个常见错误

5.1 错误 1:忘记保留被装饰函数的元信息

问题:装饰器的包装函数覆盖了被装饰函数的__name____doc__等元信息。解决方案:使用functools.wraps(func)装饰包装函数。

5.2 错误 2:被装饰函数有返回值但未处理

问题:包装函数未返回被装饰函数的结果,导致调用者无法获取返回值。解决方案:在包装函数中保存func(*args, **kwargs)的结果,并返回。

5.3 错误 3:带参数的装饰器实现层级错误

问题:混淆了装饰器参数、被装饰函数、包装函数的层级关系。解决方案:带参数的装饰器需三层嵌套:参数接收层→函数接收层→包装逻辑层。

5.4 错误 4:装饰器嵌套执行顺序理解错误

问题:多个装饰器嵌套时,执行顺序不符合预期。解决方案:记住 "就近原则":靠近函数的装饰器先执行,远离函数的后执行(等价于函数嵌套调用)。

5.5 错误 5:异步函数使用同步装饰器

问题:用同步装饰器装饰异步函数,导致await关键字无法使用,或函数无法正常执行。解决方案:异步函数装饰器的包装函数必须是异步的(async def),并使用await调用被装饰函数。

5.6 错误 6:缓存装饰器用于不可哈希参数

问题:将缓存装饰器用于参数为列表、字典等不可哈希类型的函数,导致报错。解决方案:确保缓存的 key 是可哈希的,或在装饰器中处理不可哈希参数(如转换为字符串)。

5.7 错误 7:装饰器导致函数递归调用自身

问题:在装饰器中直接调用func(),而func已被装饰器替换,导致递归调用。解决方案:确保在包装函数中调用的是原始的func(未被装饰的函数)。

5.8 错误 8:权限验证装饰器未处理用户上下文

问题:装饰器中硬编码用户信息,无法适应多用户场景。解决方案:从全局上下文、函数参数或配置中动态获取用户信息。

5.9 错误 9:重试装饰器未指定异常类型

问题:重试装饰器捕获所有异常,包括逻辑错误(如参数错误),导致无效重试。解决方案:通过exceptions参数指定需要重试的异常类型。

5.10 错误 10:装饰器的副作用未清理

问题:装饰器中创建的资源(如文件句柄、数据库连接)未及时清理,导致资源泄露。解决方案:使用try-finally或上下文管理器确保资源清理。

六、实战项目:用装饰器打造一个简易接口框架

结合前面的知识点,我们用装饰器开发一个简易的 HTTP 接口框架,支持路由注册、权限验证、日志记录、参数校验等核心功能。

6.1 项目结构

simple_api/
└── main.py  # 框架核心代码

6.2 完整代码实现

import functools
import logging
import json
from typing import Dict, Type

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

# 全局变量:存储路由映射和权限配置
routes = {}  # {url: (func, required_role)}
current_user = {"username": "admin", "role": "admin"}  # 模拟当前用户

# ------------------------------
# 装饰器1:路由注册
# ------------------------------
def route(url: str, required_role: str = "guest"):
    """路由注册装饰器:绑定URL与处理函数,并指定所需权限"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        # 注册路由
        routes[url] = (wrapper, required_role)
        logging.info(f"注册路由:{url},所需权限:{required_role}")
        return wrapper
    return decorator

# ------------------------------
# 装饰器2:权限验证
# ------------------------------
def permission_check(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 获取当前路由的所需权限
        url = args[0]  # 假设第一个参数是URL
        _, required_role = routes[url]
        if current_user["role"] != required_role:
            return json.dumps({"code": 403, "msg": f"权限不足,需要{required_role}角色"})
        logging.info(f"用户 {current_user['username']} 权限验证通过")
        return func(*args, **kwargs)
    return wrapper

# ------------------------------
# 装饰器3:日志记录
# ------------------------------
def api_log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        url = args[0]
        logging.info(f"收到请求:{url},参数:{kwargs}")
        start_time = time.time()
        try:
            result = func(*args, **kwargs)
            logging.info(f"请求 {url} 处理成功,耗时:{time.time() - start_time:.4f}秒")
            return result
        except Exception as e:
            logging.error(f"请求 {url} 处理失败:{str(e)}")
            return json.dumps({"code": 500, "msg": "服务器内部错误"})
    return wrapper

# ------------------------------
# 装饰器4:参数校验
# ------------------------------
def validate_params(param_rules: Dict[str, Type]):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 验证参数
            for param_name, param_type in param_rules.items():
                if param_name not in kwargs:
                    return json.dumps({"code": 400, "msg": f"缺少参数:{param_name}"})
                if not isinstance(kwargs[param_name], param_type):
                    return json.dumps({
                        "code": 400,
                        "msg": f"参数 {param_name} 类型错误,预期 {param_type.__name__}"
                    })
            return func(*args, **kwargs)
        return wrapper
    return decorator

# ------------------------------
# 核心:请求处理函数
# ------------------------------
@api_log
@permission_check
def handle_request(url: str, **kwargs):
    """处理HTTP请求:根据URL路由到对应的处理函数"""
    if url not in routes:
        return json.dumps({"code": 404, "msg": "接口不存在"})
    func, _ = routes[url]
    return func(url, **kwargs)

# ------------------------------
# 定义接口(使用装饰器)
# ------------------------------
# 1. 公开接口(无需特殊权限)
@route("/api/hello", required_role="guest")
def hello_handler(url, **kwargs):
    name = kwargs.get("name", "匿名用户")
    return json.dumps({"code": 200, "msg": f"Hello, {name}!"})

# 2. 需要管理员权限的接口
@route("/api/admin/delete", required_role="admin")
@validate_params({"id": int})
def delete_handler(url, **kwargs):
    id = kwargs["id"]
    return json.dumps({"code": 200, "msg": f"成功删除ID为{id的数据}"})

# 3. 需要编辑者权限的接口
@route("/api/editor/edit", required_role="editor")
@validate_params({"id": int, "content": str})
def edit_handler(url, **kwargs):
    id = kwargs["id"]
    content = kwargs["content"]
    return json.dumps({"code": 200, "msg": f"成功编辑ID为{id}的数据,内容:{content}"})

# ------------------------------
# 测试接口
# ------------------------------
if __name__ == "__main__":
    import time

    # 测试1:访问公开接口
    print("测试1:访问公开接口")
    print(handle_request("/api/hello", name="Python"))
    print("-" * 30)

    # 测试2:管理员访问删除接口(参数正确)
    print("测试2:管理员访问删除接口")
    print(handle_request("/api/admin/delete", id=1001))
    print("-" * 30)

    # 测试3:管理员访问删除接口(参数错误)
    print("测试3:管理员访问删除接口(参数错误)")
    print(handle_request("/api/admin/delete", id="abc"))
    print("-" * 30)

    # 测试4:普通用户访问管理员接口(权限不足)
    current_user["role"] = "guest"
    print("测试4:普通用户访问管理员接口")
    print(handle_request("/api/admin/delete", id=1001))
    print("-" * 30)

    # 测试5:访问不存在的接口
    print("测试5:访问不存在的接口")
    print(handle_request("/api/nonexistent"))

6.3 运行结果

2024-05-20 15:00:00,000 - INFO - 注册路由:/api/hello,所需权限:guest
2024-05-20 15:00:00,000 - INFO - 注册路由:/api/admin/delete,所需权限:admin
2024-05-20 15:00:00,000 - INFO - 注册路由:/api/editor/edit,所需权限:editor
测试1:访问公开接口
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/hello,参数:{'name': 'Python'}
2024-05-20 15:00:00,000 - INFO - 用户 admin 权限验证通过
2024-05-20 15:00:00,000 - INFO - 请求 /api/hello 处理成功,耗时:0.0000秒
{"code": 200, "msg": "Hello, Python!"}
------------------------------
测试2:管理员访问删除接口
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/admin/delete,参数:{'id': 1001}
2024-05-20 15:00:00,000 - INFO - 用户 admin 权限验证通过
2024-05-20 15:00:00,000 - INFO - 请求 /api/admin/delete 处理成功,耗时:0.0000秒
{"code": 200, "msg": "成功删除ID为1001的数据"}
------------------------------
测试3:管理员访问删除接口(参数错误)
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/admin/delete,参数:{'id': 'abc'}
2024-05-20 15:00:00,000 - INFO - 用户 admin 权限验证通过
2024-05-20 15:00:00,000 - INFO - 请求 /api/admin/delete 处理成功,耗时:0.0000秒
{"code": 400, "msg": "参数 id 类型错误,预期 int"}
------------------------------
测试4:普通用户访问管理员接口
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/admin/delete,参数:{'id': 1001}
2024-05-20 15:00:00,000 - INFO - 请求 /api/admin/delete 处理成功,耗时:0.0000秒
{"code": 403, "msg": "权限不足,需要admin角色"}
------------------------------
测试5:访问不存在的接口
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/nonexistent,参数:{}
2024-05-20 15:00:00,000 - INFO - 请求 /api/nonexistent 处理成功,耗时:0.0000秒
{"code": 404, "msg": "接口不存在"}

6.4 项目亮点

  1. 装饰器解耦:路由、权限、日志、参数校验等功能通过装饰器实现,与业务逻辑分离;
  2. 可扩展性强:新增接口只需添加@route装饰器,新增功能只需添加对应的装饰器;
  3. 模拟真实场景:涵盖 Web 开发中常见的核心功能,可直接扩展为真实的 HTTP 接口服务。

七、总结与进阶学习

7.1 装饰器核心要点总结

  1. 本质:装饰器是 "函数包装器",通过函数嵌套 / 类实现,核心是 "无侵入式增强函数功能";
  2. 核心特性:不修改原函数代码、不改变函数调用方式,支持嵌套、参数传递、元信息保留;
  3. 应用场景:日志记录、性能计时、缓存控制、权限验证、参数校验、异常处理等;
  4. 关键工具functools.wraps(保留元信息)、functools.lru_cache(内置缓存装饰器)。

7.2 进阶学习方向

  1. 深入学习functools模块:掌握partial(函数参数绑定)、singledispatch(单分派泛型函数)等高级工具;
  2. 框架源码中的装饰器:阅读 Django、Flask、FastAPI 等框架的源码,学习装饰器在实际框架中的应用;
  3. 自定义带状态的装饰器:结合类装饰器和闭包,实现更复杂的状态管理(如限流装饰器的调用次数统计);
  4. 装饰器与设计模式:理解装饰器模式与代理模式、适配器模式的区别与联系;
  5. 异步装饰器进阶:学习asyncio模块的高级特性,实现支持异步上下文的装饰器。

7.3 学习建议

装饰器的学习重点在于 "理解原理 + 多练实战"。建议先从简单的函数装饰器入手,掌握基础逻辑后,逐步尝试带参数的装饰器、类装饰器,最后通过实战项目整合所学知识。

记住:装饰器的核心价值是 "解耦"—— 将通用功能与业务逻辑分离,让代码更简洁、更易维护。在实际开发中,不要为了使用装饰器而使用,而是当遇到 "多个函数需要相同增强功能" 时,再考虑用装饰器解决。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值