Python案例如何自定义装饰器?

wen python案例 10

Python案例:如何自定义装饰器?从零到精通的实战指南

📚 目录导读

  1. 什么是装饰器?为什么需要它?
  2. Python装饰器的核心原理
  3. 手把手:第一个自定义装饰器
  4. 进阶:带参数的装饰器
  5. 实战案例:日志、性能测试、权限校验
  6. 常见踩坑与解决方案
  7. 问答环节:你关心的问题都在这

什么是装饰器?为什么需要它?

装饰器(Decorator) 是Python中一种特殊的函数,它允许你在不修改原函数代码的前提下,为函数增加额外的功能,装饰器就像给函数“套上一层包装”——比如给一个普通函数穿上“日志外衣”或“性能监控夹克”。

Python案例如何自定义装饰器?

典型场景:

  • 记录函数调用日志
  • 计算函数执行时间
  • 权限验证(如检查用户是否登录)
  • 缓存函数结果
  • 异常重试机制

:装饰器和普通函数调用有什么区别?
:装饰器是声明式的——你只需在函数定义前加一行@装饰器名,后续调用时自动增强,而普通函数调用需要你手动在每处调用前后写额外代码,导致重复、难以维护。


Python装饰器的核心原理

装饰器的本质是 高阶函数(接受一个函数作为参数,并返回一个新函数),其底层原理可用以下代码表示:

def decorator(func):
    def wrapper(*args, **kwargs):
        # 增强功能
        return func(*args, **kwargs)
    return wrapper
# 使用装饰器
@decorator
def say_hello():
    print("Hello!")
# 等价于:
say_hello = decorator(say_hello)

关键点:

  • wrapper函数接收原函数的所有参数(*args, **kwargs
  • wrapper内部调用原函数并返回结果
  • 装饰器返回wrapper对象,替换原函数

:为什么wrapper中的*args, **kwargs是必须的?
:原函数可能有不同数量的参数,使用通用写法可以让装饰器适配任何函数,否则参数不匹配时会报错。


手把手:第一个自定义装饰器

案例:简单的“执行时间统计”装饰器

import time
def timer_decorator(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"⏱️ {func.__name__} 执行耗时:{end - start:.4f}秒")
        return result
    return wrapper
@timer_decorator
def compute_sum(n):
    total = sum(range(n))
    return total
# 调用
result = compute_sum(1000000)
print(f"结果:{result}")

输出示例

⏱️ compute_sum 执行耗时:0.0321秒
结果:499999500000

优化提示:使用functools.wraps保留原函数元信息(如__name____doc__):

from functools import wraps
def timer_decorator(func):
    @wraps(func)  # 关键!保留原函数信息
    def wrapper(*args, **kwargs):
        ...
    return wrapper

:不加@wraps会怎样?
:被装饰后,compute_sum.__name__会变成wrapper,导致调试混乱,@wraps能修复此问题。


进阶:带参数的装饰器

有时我们需要给装饰器传递参数(如日志级别、重试次数),这需要额外一层嵌套:

def retry_decorator(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"尝试第{attempt}次失败:{e}")
                    if attempt == max_attempts:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator
@retry_decorator(max_attempts=5, delay=2)
def unstable_api():
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络波动")
    return "成功"
print(unstable_api())

解析:三层结构:

  1. retry_decorator(max_attempts, delay) 返回decorator
  2. decorator(func) 返回wrapper
  3. wrapper 内部执行重试逻辑

:如何让装饰器支持@装饰器@装饰器(参数)两种写法?
:可以使用functools.partial判断参数类型,但更简单的是统一用带括号的写法,即@装饰器()


实战案例:日志、性能测试、权限校验

案例1:自动记录函数调用日志

import logging
def log_decorator(level=logging.INFO):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            logger = logging.getLogger(func.__module__)
            logger.log(level, f"调用 {func.__name__},参数:{args}, {kwargs}")
            result = func(*args, **kwargs)
            logger.log(level, f"{func.__name__} 返回:{result}")
            return result
        return wrapper
    return decorator
@log_decorator()
def divide(a, b):
    return a / b
divide(10, 2)  # 自动输出日志

案例2:类方法装饰器(权限校验)

def require_role(role):
    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            if not hasattr(self, 'user_role') or self.user_role != role:
                raise PermissionError("权限不足")
            return func(self, *args, **kwargs)
        return wrapper
    return decorator
class AdminAPI:
    def __init__(self, user_role):
        self.user_role = user_role
    @require_role('admin')
    def delete_user(self, user_id):
        print(f"删除用户 {user_id}")
api = AdminAPI('user')
api.delete_user(123)  # 抛出权限错误

:装饰器能作用于类而不是方法吗?
:可以,类装饰器接受类作为参数,返回修改后的类(如添加属性),原理与函数装饰器一致。


常见踩坑与解决方案

问题 表现 解决
丢失元信息 func.__name__变成wrapper 添加@wraps(func)
装饰器顺序问题 多层装饰器执行顺序混乱 从上到下装饰,执行时从下到上
参数不兼容 原函数有默认参数时出错 确保wrapper使用*args, **kwargs
类中self丢失 被装饰方法调用时self为空 检查wrapper是否传递了self
装饰器内部异常 异常未正确处理 使用try/except包裹原函数调用

多层装饰器示例

@decorator_A
@decorator_B
def func(): ...
# 等效于:func = decorator_A(decorator_B(func))

执行时先执行最外层装饰器(A)的额外代码,然后进入B的额外代码,最后执行原函数。


问答环节:你关心的问题都在这

Q1:装饰器可以修改函数签名吗?

A:技术上可以,但强烈不推荐,修改签名会导致IDE提示不准确、文档混乱,建议保持原函数行为不变,只增加额外功能。

Q2:如何调试被装饰的函数?

A:使用@wraps保留原信息,或者直接print(被装饰函数.__wrapped__)获取原始函数。

Q3:装饰器适合用在哪些生产场景?

A:日志记录、性能监控、缓存(如functools.lru_cache)、重试机制、数据库连接管理、事务控制、权限校验等,大型框架如Flask、Django的@app.route@login_required本质上都是装饰器。

Q4:有没有性能问题?

A:每次调用都会经过wrapper,但额外开销通常微乎其微(微秒级别),如果极端注重性能(如每秒百万次调用),可考虑用functools.partial替代。

Q5:如何查看一个函数被哪些装饰器装饰过?

A:递归检查__wrapped__属性链:

def unwrap_func(func):
    while hasattr(func, '__wrapped__'):
        func = func.__wrapped__
    return func

自定义装饰器是Python高级特性中最实用的技能之一,掌握它,你将能够:

  • ✅ 用几行代码替代重复的增强逻辑
  • ✅ 让代码更模块化、更易维护
  • ✅ 深入理解“函数即对象”的Python哲学

建议练习路径:从简单的日志装饰器开始 → 尝试带参数的重试装饰器 → 结合类编写权限装饰器 → 阅读Flask源码中的装饰器实现,每多写一个装饰器,你对Python的理解就会加深一层。

抱歉,评论功能暂时关闭!