Python案例中的装饰器怎么使用?

wen python案例 8

Python装饰器实战指南:从入门到精通,掌握函数增强的终极武器

📑 目录导读

  1. 装饰器是什么?为什么Python开发者必须掌握?
  2. 装饰器的核心原理:从闭包到语法糖
  3. 基础案例:函数执行时间统计器
  4. 进阶案例:带参数的装饰器(日志系统)
  5. 实战案例:Web开发中的权限校验装饰器
  6. 高级技巧:类装饰器与functools.wraps
  7. 常见误区与优化建议
  8. Q&A:解答你关于装饰器的5个高频疑问

装饰器是什么?为什么Python开发者必须掌握?

装饰器(Decorator)是Python中一种用于修改函数或类行为的语法结构,它允许在不改变原有代码的情况下,动态地给函数添加额外功能,装饰器就是“在不修改函数本体的情况下,给函数穿上一件带有附加功能的外衣”。

Python案例中的装饰器怎么使用?

为什么必须掌握?

  • 代码复用:将日志、计时、权限校验等横切关注点抽象为装饰器,避免重复代码
  • 保持函数纯净:业务逻辑与辅助功能解耦,符合单一职责原则
  • 面试高频考点:90%的Python高级岗位面试都会考察装饰器原理与实战
  • 框架必备技能:Flask、Django、FastAPI等主流框架广泛使用装饰器(如@app.route

装饰器的核心原理:从闭包到语法糖

1 闭包的基础回顾

闭包是指一个函数内部定义了另一个函数,并且内部函数可以访问外部函数的变量,装饰器正是基于闭包实现的。

def outer(func):
    def inner():
        print("执行前")
        func()  # 调用原始函数
        print("执行后")
    return inner

2 手动使用装饰器 vs 语法糖

# 不使用语法糖
def greet():
    print("Hello!")
greet = outer(greet)  # 手动装饰
greet()
# 使用语法糖 @
@outer
def greet():
    print("Hello!")
greet()

核心机制@outer 等价于 greet = outer(greet),Python解释器在解析到该行代码时自动执行函数替换。


基础案例:函数执行时间统计器

场景:需要快速测量某个函数的执行耗时,用于性能分析。

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 slow_function(n):
    time.sleep(n)
    return f"休眠了{n}秒"
# 测试
slow_function(2)  # 输出: slow_function 执行耗时: 2.0001秒

关键点

  • 使用 *args, **kwargs 确保装饰器能适配任意签名的函数
  • result = func(...) 保证原始函数的返回值被传递
  • wrapper 函数签名与原始函数不同,需要用到后面的 functools.wraps

进阶案例:带参数的装饰器(日志系统)

场景:实现一个可以自定义日志级别的日志记录装饰器(如 @log(level="INFO"))。

def log(level="DEBUG"):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[{level}] 调用函数: {func.__name__}")
            result = func(*args, **kwargs)
            print(f"[{level}] 函数返回: {result}")
            return result
        return wrapper
    return decorator
@log(level="ERROR")
def divide(a, b):
    return a / b
divide(10, 2)
# 输出:
# [ERROR] 调用函数: divide
# [ERROR] 函数返回: 5.0

原理:带参数的装饰器本质上是三层嵌套函数:

  1. 外层函数接收参数(如 level
  2. 中间层接收被装饰的函数
  3. 内层实现具体的增强逻辑

实战案例:Web开发中的权限校验装饰器

场景:模拟Flask/FastAPI中的用户权限校验,只有管理员才能执行某些操作。

# 模拟用户数据库
users = {"admin": {"role": "admin"}, "user": {"role": "user"}}
def check_permission(required_role="admin"):
    def decorator(func):
        def wrapper(username, *args, **kwargs):
            user = users.get(username)
            if not user:
                print("用户不存在")
                return None
            if user["role"] != required_role:
                print(f"权限不足,需要{required_role}角色")
                return None
            return func(username, *args, **kwargs)
        return wrapper
    return decorator
@check_permission(required_role="admin")
def delete_user(admin_name, target_user):
    print(f"管理员 {admin_name} 删除了用户 {target_user}")
    return True
# 测试
delete_user("admin", "user1")   # 成功
delete_user("user", "user1")    # 输出: 权限不足

实际应用:Django中的 @login_required、Flask中的 @login_required 都是类似实现。


高级技巧:类装饰器与functools.wraps

1 使用functools.wraps保留元信息

装饰器会改变函数的 __name____doc__ 等属性,使用 @wraps 可以解决:

from functools import wraps
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """这是wrapper的文档"""
        print("执行前")
        return func(*args, **kwargs)
    return wrapper
@my_decorator
def example():
    """这是example的文档"""
    print("Hello")
print(example.__name__)   # example(不加wraps会输出wrapper)
print(example.__doc__)    # 这是example的文档

2 类装饰器(用类来实现装饰器)

当装饰器需要维护状态时,类装饰器更合适:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.calls = 0
    def __call__(self, *args, **kwargs):
        self.calls += 1
        print(f"调用次数: {self.calls}")
        return self.func(*args, **kwargs)
@CountCalls
def hello():
    print("Hello!")
hello()  # 调用次数: 1
hello()  # 调用次数: 2

常见误区与优化建议

❌ 常见误区

  1. 装饰器顺序问题:多个装饰器堆叠时,执行顺序是从下到上(离函数最近的先执行)

    @decorator1
    @decorator2
    def func(): pass
    # 先执行decorator2,再执行decorator1
  2. *忘记`args, kwargs`:导致装饰器无法处理带参数的函数

  3. 忽略functools.wraps:导致调试困难(函数名丢失)

✅ 优化建议

  • 定义装饰器时始终使用 functools.wraps
  • 使用类装饰器管理复杂状态
  • 考虑使用 functools.lru_cache 作为内置装饰器学习范例

Q&A:解答你关于装饰器的5个高频疑问

Q1:装饰器和闭包有什么区别?
A:闭包是装饰器实现的基础,闭包强调内部函数可以访问外部变量,而装饰器是闭包的一种特定应用——用于修改函数行为。

Q2:装饰器可以装饰类方法吗?
A:可以,但要特别注意 self 参数的传递,在类方法中使用装饰器时,self 会作为第一个参数自动传递。

Q3:如何用装饰器实现单例模式?
A:通过装饰器维护一个字典缓存实例:

def singleton(cls):
    instances = {}
    @wraps(cls)
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

Q4:装饰器性能有什么影响?
A:每次调用装饰的函数都需经过 wrapper 层,会有微小的性能损耗,对于高频调用的函数,建议使用 functools.lru_cache 或直接内联代码。

Q5:能不能用装饰器修改函数的返回值?
A:完全可以,你可以在 wrapper 中拦截返回值并修改,甚至捕获异常返回默认值。


延伸阅读

  • Python官方文档 - 装饰器章节
  • Flask源码中的 @app.route 实现
  • Django中间件与装饰器的协同工作模式

(本文为SEO优化原创内容,结合Bing与Google排名规则撰写,未包含任何第三方域名推荐)

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