当前位置: 首页 > news >正文

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():passprint("模块加载完成")

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

模块加载中...
装饰器执行:装饰 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 resultreturn wrapperreturn decorator# 使用带参数的装饰器
@log_decorator(level="INFO")
def add(a, b):return a + bprint(add(1, 2))

执行结果:

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

2.2 类装饰器

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

class TimerDecorator:def __init__(self, func):  # 初始化时接收原函数self.func = funcdef __call__(self, *args, **kwargs):  # 调用时执行装饰逻辑import timestart = 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 timetime.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 resultreturn wrapperdef decorator_b(func):def wrapper(*args, **kwargs):print("装饰器 B 开始")result = func(*args, **kwargs)print("装饰器 B 结束")return resultreturn 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 functoolsdef 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 的文档字符串"""passprint(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 resultreturn wrapper# 定义类,用装饰器装饰实例方法
class User:def __init__(self, name):self.name = name  # 实例属性# 用装饰器装饰实例方法@log_instance_methoddef 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_positivedef 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 resultreturn wrapper# 定义类,用装饰器装饰静态方法
class StringUtils:# 装饰器顺序:@staticmethod 在下,功能装饰器在上@staticmethod@timerdef 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, Optionaldef 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 defaultreturn wrapperreturn 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()
    
http://www.hskmm.com/?act=detail&tid=37878

相关文章:

  • 2025年10月中国电线电缆厂家推荐榜:五强性能评价
  • R-高性能编程-全-
  • oracle NVL和NVL2
  • SSH 端口转发与跳板机
  • 99.5%制氧机生产厂家盘点!2025高原制氧机厂家排行榜
  • 2025年中国国际健康营养博览会(NHNE):深度解析亚洲旗舰展的供需对接效能
  • 利用 ProxyJump 来安全访问内网主机
  • 2025年10月上海装修公司实力榜:排名与口碑全解析
  • sqlserver 字符串转成日期、日期转字符串、字符串转数字、数字转字符串
  • 2025年10月上海装修公司对比榜:十家真实评价排行
  • 2025年10月上海装修公司服务榜:口碑排行十强
  • 2025年10月PE管厂家评价榜:五强参数与案例全解析
  • 增强AI股票预测分析报告 - 2025年10月24日
  • CAD燕秀工具箱2.88中文正式版下载与安装教程
  • 2025年10月中国宝宝辅食品牌对比榜:十强实测数据全公开
  • 2025 最新推荐!碳纤维布厂家权威榜单:聚焦技术与口碑,助您精准选型
  • 湖北炸鸡品牌店加盟公司口碑榜:TOP3榜单深度解析与数据全景
  • 2025年10月长白山亲子酒店推荐:亲子榜评测指南
  • oracle表空间扩容
  • 高性能AI股票预测分析报告 - 2025年10月24日 - 09:50:03
  • 2025年10月北京口腔医院评测榜:十家机构对比
  • Microsoft AI Genius | 从数据混乱到智能洞察:用前沿技术构建一体化 AI 工作流
  • 2025 年度视频引伸计厂家最新推荐榜:深度解析实力品牌与新锐势力,助科研与工业检测精准选型
  • 2025年10月小红书代运营公司推荐榜:五强评测与选择策略
  • 2025 三维全场应变测量系统厂家最新推荐榜:核心竞争力解析与优质品牌优选指南
  • 2025年10月中国丝绸选购榜:十家口碑排行全解析
  • 2025年10月进度管理工具推荐:信创生态榜性能对比排行
  • 2025年10月法律咨询律所推荐榜:盈科国内外分所规模对比榜
  • 2025年10月蒸汽发生器品牌推荐榜:五强参数与场景适配全解析
  • 国产化替代首选!2025自主可控的国产制品管理平台推荐