Python 装饰器

标签: #Python #装饰器 #函数装饰器 #类装饰器
学习周期:1 天 | 核心目标:掌握装饰器原理,能独立编写函数装饰器、带参数装饰器和类装饰器


3.3 装饰器

装饰器(Decorator)是 Python 中极具实用性的语法糖,核心作用是 在不修改原函数/类代码、不改变其调用方式的前提下,为其增加额外功能(如日志记录、执行计时、权限校验、异常捕获等)。

其底层依赖 闭包 和 函数是一等对象 的特性(函数可作为参数传递、作为返回值返回,可赋值给变量),是“开闭原则”(对扩展开放、对修改关闭)的典型实现。


3.3.1 函数装饰器(基础)

核心逻辑

函数装饰器本质是一个函数,它接收一个函数(被装饰的原函数)作为参数,返回一个新的函数(增强后的函数),最终用新函数替代原函数。

三步实现
  1. 定义“装饰器函数”:接收原函数作为参数。
  2. 在装饰器函数内部,定义“新函数”(wrapper):实现增强功能 + 调用原函数。
  3. 装饰器函数返回“新函数”。
基础示例:日志装饰器
def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"【日志】函数 {func.__name__} 开始执行...")
        result = func(*args, **kwargs)
        print(f"【日志】函数 {func.__name__} 执行结束!")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

add(1, 2)  # 输出日志并返回 3

关键细节

  • *args, **kwargs:保证装饰器可接收任意参数,通用性强。
  • 必须返回原函数的结果,否则调用后会丢失返回值。
  • 装饰器在 @ 语法时立即执行,原函数被替换为 wrapper。
保留原函数元信息(functools.wraps

装饰后原函数的 __name____doc__ 会被覆盖,使用 functools.wraps 可解决。

import functools

def log_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper
多个装饰器的执行顺序

装饰器叠加时,执行顺序从下往上(靠近函数的先执行)。

@decorator_a
@decorator_b
def func(): pass
# 等价于 func = decorator_a(decorator_b(func))

3.3.2 带参数的装饰器

当装饰器需要接收额外参数(如日志级别、重试次数)时,需要三层嵌套函数。

结构解析
def decorator_with_args(arg1, arg2):
    def real_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 使用 arg1, arg2
            return func(*args, **kwargs)
        return wrapper
    return real_decorator

@decorator_with_args("hello", 42)
def foo(): pass
实用示例:日志级别控制
def log(level="INFO"):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{level}] 调用 {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log(level="DEBUG")
def debug_func(): pass
权限校验装饰器
def permission_check(required_role):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user_role, *args, **kwargs):
            if user_role == required_role:
                return func(*args, **kwargs)
            else:
                print(f"权限不足,需要 {required_role} 角色")
                return None
        return wrapper
    return decorator

3.3.3 类装饰器

类装饰器通过实现 __init__ 和 __call__ 方法达到装饰效果,适合需要保存状态的复杂场景。

基础类装饰器
import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(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 say_hello(): pass
带参数的类装饰器
def repeat(times):
    class RepeatDecorator:
        def __init__(self, func):
            self.func = func
            self.times = times
            functools.update_wrapper(self, func)
        def __call__(self, *args, **kwargs):
            for _ in range(self.times):
                result = self.func(*args, **kwargs)
            return result
    return RepeatDecorator

@repeat(times=3)
def greet(): print("Hello")
类装饰器实现单例模式
class Singleton:
    def __init__(self, cls):
        self.cls = cls
        self.instance = None

    def __call__(self, *args, **kwargs):
        if self.instance is None:
            self.instance = self.cls(*args, **kwargs)
        return self.instance

@Singleton
class DatabaseConnection: pass

3.3.4 函数装饰器 vs 类装饰器

类型 实现方式 优势 适用场景
函数装饰器 两层/三层函数嵌套 实现简单、代码简洁 日志、计时、权限校验等简单场景
类装饰器 类 + __call__ 方法 可保存状态,适合复杂增强逻辑 调用计数、缓存、单例等需要状态的场景

📚 学习资料(Obsidian 可直接收藏)


🎯 学习建议(1 天计划)

  1. 上午:理解闭包概念,编写无参数装饰器(日志、计时),学会使用 functools.wraps
  2. 下午:练习带参数的装饰器、类装饰器,掌握多个装饰器的执行顺序,完成缓存、重试等实战练习。

✅ 核心要点总结

  1. 装饰器本质:高阶函数,接收函数返回新函数,使用 @ 语法糖。
  2. functools.wraps:必须使用,以保留原函数的元信息(__name____doc__)。
  3. 带参数的装饰器:三层嵌套函数,最外层接收装饰器参数,内层接收函数,最内层接收函数参数。
  4. 类装饰器:通过 __init__ 接收被装饰函数(或参数),通过 __call__ 实现包装逻辑,适合保存状态。
  5. 执行顺序:多个装饰器从下往上执行(靠近函数的最先被包装)。
  6. 应用场景:日志、计时、缓存、权限校验、重试、单例模式等。

练习题(自测)

  1. 编写一个装饰器 uppercase,将函数的返回值转换为大写字符串(假设返回值是字符串)。
  2. 编写一个带参数的装饰器 repeat(n),让被装饰函数重复执行 n 次。
  3. 使用类装饰器实现一个 Deprecated 装饰器,当调用被装饰函数时打印警告“该函数已过时”。
  4. 写一个装饰器 memoize,为函数添加缓存功能(手动实现,不使用 functools.lru_cache),支持任意参数。
  5. 使用装饰器为以下函数添加执行时间统计,并比较不同实现的性能差异:计算 1 到 1000000 的和(使用循环 vs 使用 sum)。

建议将上述代码在本地环境中运行,尝试修改参数并观察装饰器的行为。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐