Python 装饰器

Python 装饰器(Decorator)是一种高级语法,用于在不修改原函数代码的前提下,动态扩展函数或类的功能。它本质上是一个接收函数 / 类作为参数,并返回新函数 / 类的可调用对象,是函数式编程的典型应用。

1、核心原理

装饰器的核心依赖于 Python 的两个特性:

  1. 函数:函数可以作为参数传递、作为返回值返回、赋值给变量。
  2. 闭包:嵌套函数可以访问外层函数的变量和参数(即使外层函数已执行完毕)。

1.1 装饰器的基本结构

一个最简单的装饰器由外层函数(接收原函数)内层函数(包装原函数) 组成,最终返回内层函数:

def decorator(func):  # 外层函数:接收原函数作为参数     def wrapper(*args, **kwargs):  # 内层函数:包装原函数(*args, **kwargs 兼容任意参数)         print("装饰器逻辑:执行前")  # 扩展功能(执行前)         result = func(*args, **kwargs)  # 调用原函数         print("装饰器逻辑:执行后")  # 扩展功能(执行后)         return result  # 返回原函数结果     return wrapper  # 返回内层函数  # 使用装饰器(语法糖:@装饰器名) @decorator def target_func():     print("原函数逻辑")  # 调用被装饰的函数 target_func() 

执行结果:

装饰器逻辑:执行前 原函数逻辑 装饰器逻辑:执行后 

说明@decorator 等价于 target_func = decorator(target_func),即原函数被装饰器返回的 wrapper 函数替换。

1.2 装饰器的执行时机

装饰器在模块加载时(导入时) 就会执行,而非函数调用时。例如:

print("模块加载中...")  def decorator(func):     print(f"装饰器执行:装饰 {func.__name__}")     return func  @decorator def func1():     pass  @decorator def func2():     pass  print("模块加载完成") 

执行结果(先执行装饰器,再执行后续代码):

模块加载中... 装饰器执行:装饰 func1 装饰器执行:装饰 func2 模块加载完成 

2、进阶用法

2.1 带参数的装饰器

若需要给装饰器传递参数(如日志的级别、缓存的过期时间),需在外层再套一层 “参数接收函数”:

def log_decorator(level):  # 最外层:接收装饰器参数     def decorator(func):  # 中间层:接收原函数         def wrapper(*args, **kwargs):             print(f"[{level}] 函数 {func.__name__} 开始执行")  # 使用装饰器参数             result = func(*args, **kwargs)             print(f"[{level}] 函数 {func.__name__} 执行结束")             return result         return wrapper     return decorator  # 使用带参数的装饰器 @log_decorator(level="INFO") def add(a, b):     return a + b  print(add(1, 2)) 

执行结果:

[INFO] 函数 add 开始执行 [INFO] 函数 add 执行结束 3 

2.2 类装饰器

类也可以作为装饰器,核心是实现 __call__ 方法(使类实例可调用):

class TimerDecorator:     def __init__(self, func):  # 初始化时接收原函数         self.func = func      def __call__(self, *args, **kwargs):  # 调用时执行装饰逻辑         import time         start = time.time()         result = self.func(*args, **kwargs)  # 调用原函数         end = time.time()         print(f"函数 {self.func.__name__} 执行耗时:{end - start:.2f}s")         return result  # 使用类装饰器 @TimerDecorator def slow_func(seconds):     import time     time.sleep(seconds)  # 模拟耗时操作  slow_func(1)  # 执行被装饰的函数 

执行结果:

函数 slow_func 执行耗时:1.00s 

2.3 装饰器的嵌套

多个装饰器可以叠加使用,嵌套装饰器的核心是装饰顺序调用顺序的区别,理解这两个顺序是掌握嵌套装饰器的关键。

# 定义两个装饰器 def decorator_a(func):     def wrapper(*args, **kwargs):         print("装饰器 A 开始")         result = func(*args, **kwargs)         print("装饰器 A 结束")         return result     return wrapper  def decorator_b(func):     def wrapper(*args, **kwargs):         print("装饰器 B 开始")         result = func(*args, **kwargs)         print("装饰器 B 结束")         return result     return wrapper  # 嵌套应用装饰器 @decorator_a @decorator_b def target():     print("执行目标函数")  # 调用目标函数 target() 

执行结果:

装饰器 A 开始 装饰器 B 开始 执行目标函数 装饰器 B 结束 装饰器 A 结束 
2.3.1 装饰顺序:从下到上(“包裹顺序”)

装饰器的应用过程发生在函数定义时,本质是对函数的 “层层包裹”。对于上面的例子:

@decorator_a @decorator_b def target(): ... 

等价于手动执行:

# 第一步:用 decorator_b 装饰原始 target,得到新函数 wrapper_b wrapper_b = decorator_b(target) # 第二步:用 decorator_a 装饰 wrapper_b,得到最终函数 wrapper_a target = decorator_a(wrapper_b) 

即:先执行下方的装饰器,再执行上方的装饰器,最终得到的 target 是最外层装饰器(decorator_a)返回的 wrapper 函数。

2.3.2 调用顺序:从外到内(“执行顺序”)

当调用被装饰的函数(如 target())时,执行流程是从最外层装饰器的 wrapper 开始,逐层进入内层,最后执行原始函数,再逐层返回

以上面的 target() 调用为例,执行流程拆解:

  1. 调用 target() 实际是调用 decorator_a 返回的 wrapper_a
  2. wrapper_a 中先执行自身逻辑(print("装饰器 A 开始")),然后调用 func(此时 funcdecorator_b 返回的 wrapper_b);
  3. wrapper_b 中先执行自身逻辑(print("装饰器 B 开始")),然后调用 func(此时 func 是原始 target 函数);
  4. 执行原始 target 函数(print("执行目标函数"));
  5. 原始函数执行完毕,返回结果给 wrapper_bwrapper_b 执行后续逻辑(print("装饰器 B 结束"));
  6. wrapper_b 执行完毕,返回结果给 wrapper_awrapper_a 执行后续逻辑(print("装饰器 A 结束"));
  7. 最终结果返回给调用者。

简言之:调用时先执行外层装饰器的 “前置逻辑”,再执行内层装饰器的 “前置逻辑”,然后是原始函数,最后按相反顺序执行各装饰器的 “后置逻辑”

2.4 保留原函数元信息

装饰器默认会覆盖原函数的元信息(如 __name____doc__),需使用 functools.wraps 修复:

import functools  def bad_decorator(func):     def wrapper():         func()     return wrapper  # 未保留元信息  def good_decorator(func):     @functools.wraps(func)  # 保留原函数元信息     def wrapper():         func()     return wrapper  @bad_decorator def f1():     """f1 的文档字符串"""     pass  @good_decorator def f2():     """f2 的文档字符串"""     pass  print(f1.__name__)  # 输出:wrapper(错误) print(f1.__doc__)   # 输出:None(错误) print(f2.__name__)  # 输出:f2(正确) print(f2.__doc__)   # 输出:f2 的文档字符串(正确) 

2.5 装饰类的方法

2.5.1 装饰实例方法

实例方法是类中最常见的方法,第一个参数默认是 self(指向实例本身)。装饰实例方法时,装饰器需要正确传递 self 及其他参数。

用装饰器记录实例方法的调用日志

import functools  # 定义装饰器:记录方法调用的参数、返回值 def log_instance_method(func):     @functools.wraps(func)  # 保留原方法的元信息(如 __name__、__doc__)     def wrapper(self, *args, **kwargs):         # 打印实例信息、方法名、参数         print(f"[日志] 实例 {self} 调用方法 {func.__name__},参数:{args},关键字参数:{kwargs}")         # 调用原始方法(注意传递 self)         result = func(self, *args, **kwargs)         # 打印返回值         print(f"[日志] 方法 {func.__name__} 返回:{result}")         return result     return wrapper  # 定义类,用装饰器装饰实例方法 class User:     def __init__(self, name):         self.name = name  # 实例属性      # 用装饰器装饰实例方法     @log_instance_method     def greet(self, prefix):         """向用户打招呼"""         return f"{prefix},我是 {self.name}"  # 测试 user = User("Alice") user.greet("你好") 

执行结果:

[日志] 实例 <__main__.User object at 0x102b5a4d0> 调用方法 greet,参数:('你好',),关键字参数:{} [日志] 方法 greet 返回:你好,我是 Alice 

关键说明:

  • 装饰器的 wrapper 函数第一个参数必须是 self(或用 *args 兼容),否则无法传递实例引用;
  • func(self, *args, **kwargs) 确保原始方法能正确接收 self 及其他参数;
  • functools.wraps(func) 保留原方法的元信息(如 user.greet.__name__ 仍为 greet,而非 wrapper)。
2.5.2 装饰类方法

类方法用 @classmethod 装饰,第一个参数默认是 cls(指向类本身)。装饰类方法时,需注意装饰器与 @classmethod 的顺序,以及 cls 参数的传递。

用装饰器验证类方法的参数

import functools  # 定义装饰器:检查类方法的参数是否为正数 def validate_positive(func):     @functools.wraps(func)     def wrapper(cls, *args, **kwargs):         # 检查所有位置参数是否为正数         for arg in args:             if not isinstance(arg, (int, float)) or arg <= 0:                 raise ValueError(f"类方法 {func.__name__} 接收无效参数:{arg}(必须为正数)")         # 调用原始类方法(传递 cls)         return func(cls, *args, **kwargs)     return wrapper  # 定义类,用装饰器装饰类方法 class MathUtils:     # 注意装饰器顺序:先应用功能装饰器,再用 @classmethod(从下到上装饰)     @classmethod     @validate_positive     def multiply(cls, a, b):         """计算两个正数的乘积(类方法)"""         return a * b  # 测试 print(MathUtils.multiply(3, 4))  # 正常调用:输出 12 MathUtils.multiply(-2, 5)  # 触发验证失败:ValueError 

执行结果:

12 ValueError: 类方法 multiply 接收无效参数:-2(必须为正数) 

关键说明:

  • 装饰器顺序:@classmethod 必须放在功能装饰器的下方(即先被装饰),因为 @classmethod 会将方法转换为类方法,功能装饰器需要装饰转换后的方法;
  • wrapper 函数第一个参数是 cls,确保原始类方法能接收类引用;
  • 类方法操作的是类本身(如 cls.__name__),而非实例,装饰器可基于类属性做校验。
2.5.3 装饰静态方法

静态方法用 @staticmethod 装饰,无默认参数(与普通函数类似)。装饰静态方法时,处理逻辑与装饰普通函数一致。

用装饰器记录静态方法的执行时间

import functools import time  # 定义装饰器:记录方法执行时间 def timer(func):     @functools.wraps(func)     def wrapper(*args, **kwargs):         start = time.time()         result = func(*args, **kwargs)  # 静态方法无 self/cls,直接传递参数         end = time.time()         print(f"[计时] 静态方法 {func.__name__} 执行耗时:{end - start:.4f} 秒")         return result     return wrapper  # 定义类,用装饰器装饰静态方法 class StringUtils:     # 装饰器顺序:@staticmethod 在下,功能装饰器在上     @staticmethod     @timer     def reverse(s):         """反转字符串(静态方法)"""         time.sleep(0.1)  # 模拟耗时操作         return s[::-1]  # 测试 print(StringUtils.reverse("hello"))  # 输出:olleh 

执行结果:

[计时] 静态方法 reverse 执行耗时:0.1002 秒 olleh 

关键说明:

  • 静态方法无 selfcls,装饰器的 wrapper 函数直接用 *args, **kwargs 接收参数即可;
  • 装饰器顺序与类方法类似:@staticmethod 在下,功能装饰器在上,确保装饰的是静态方法。

3、装饰器的应用场景

  1. 日志记录:自动记录函数的调用参数、返回值、执行时间。

  2. 权限验证:调用函数前检查用户权限(如登录状态)。

    def login_required(func):     @functools.wraps(func)     def wrapper(user, *args, **kwargs):         if not user.is_login:             raise PermissionError("请先登录")         return func(user, *args, **kwargs)     return wrapper  @login_required def pay(user, amount):     print(f"{user.name} 支付 {amount} 元") 
  3. 缓存结果:缓存函数的计算结果,避免重复计算。

    from functools import lru_cache  @lru_cache(maxsize=128) def expensive_calculation(x, y):     # 模拟耗时计算     time.sleep(1)     return x * y + x**2 + y**2 # 第一次调用会计算 result1 = expensive_calculation(10, 20) # 第二次相同参数的调用会直接从缓存返回 result2 = expensive_calculation(10, 20) 
  4. 异常处理:统一捕获函数执行中的异常,避免程序崩溃。

    import functools from typing import Callable, Any, Type, Tuple, Optional  def exception_handler(     exceptions: Tuple[Type[Exception], ...] = (Exception,),     default: Any = None,     re_raise: bool = False,     error_msg: Optional[str] = None ) -> Callable:     """     异常处理装饰器:捕获指定异常并统一处理          :param exceptions: 需要捕获的异常类型(元组),默认捕获所有Exception     :param default: 异常发生时返回的默认值(若re_raise为False)     :param re_raise: 捕获异常后是否重新抛出,默认False(不抛出)     :param error_msg: 自定义错误提示信息,默认为异常自带消息     :return: 装饰后的函数     """     def decorator(func: Callable) -> Callable:         @functools.wraps(func)  # 保留原函数元信息         def wrapper(*args: Any, **kwargs: Any) -> Any:             try:                 # 执行原函数                 return func(*args, **kwargs)             except exceptions as e:                 # 若需要重新抛出异常,交给上层处理                 if re_raise:                     raise  # 保持原异常类型和堆栈                                  # 否则返回默认值                 return default         return wrapper     return decorator  # 示例1:捕获ValueError和TypeError,返回默认值 @exception_handler(     exceptions=(ValueError, TypeError),     default=-1,     error_msg="数值转换失败" ) def parse_number(s: str) -> int:     """将字符串转换为整数"""     return int(s)   # 示例2:捕获IOError,重新抛出异常(让调用者处理) @exception_handler(     exceptions=(IOError,),     re_raise=True,     error_msg="文件读取失败" ) def read_file(path: str) -> str:     """读取文件内容"""     with open(path, "r") as f:         return f.read() 
发表评论

评论已关闭。

相关文章