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

Python函数:装饰器

一、装饰器的定义(什么是装饰器)

1)装饰器(Decorator)是一种特殊的函数,它的主要作用是在不修改被装饰函数的源代码和调用方式的前提下,为函数增加额外功能

2)本质:高阶函数 + 闭包。

形式表达

给定一个函数 func,装饰器 decorator 的本质是:decorator(func)→wrapper

其中,wrapper 是对 func 的包装,通常在调用时会执行额外操作,然后调用原函数 func。

逐步拆解

1)装饰器是一个函数,它接收另一个函数作为参数,比如 func

2)装饰器内部定义了一个新的函数 wrapper,这个函数会在调用时:先执行一些额外的操作(比如打印日志、权限检查等)、再调用传入的原函数 func

3)装饰器返回这个新的函数 wrapper,代替了原来的函数。

举例说明

def decorator(func):def wrapper():print("调用函数前的操作")func()  # 调用原函数print("调用函数后的操作")return wrapper  # 返回包装后的函数def say_hello():print("Hello!")# 使用装饰器包装函数
say_hello = decorator(say_hello)say_hello()
"""
结果:
调用函数前的操作
Hello!
调用函数后的操作
"""

解释

1)decorator(say_hello) 返回了一个新的函数 wrapper,这个函数包裹了原来的 say_hello。

2)当你调用 say_hello() 时,实际上执行的是 wrapper(),它先打印“调用函数前的操作”,然后调用原函数 say_hello(),最后打印“调用函数后的操作”。


二、装饰器有什么用

  • 增强函数功能
    在函数执行前后添加额外操作,比如日志记录、权限校验、性能统计等。
  • 代码复用与解耦
    把通用的功能抽象成装饰器,多个函数复用,避免重复代码。
  • 函数行为的动态修改
    在运行时动态地改变函数行为,而无需修改函数本身代码。
  • 简化代码结构
    通过装饰器,代码结构更清晰,主业务逻辑和辅助功能分离。

为什么要使用装饰器

  • 避免代码重复
    例如,多个函数都需要日志打印或权限检查,用装饰器封装一次,复用多处。
  • 统一管理横切关注点
    许多功能(如缓存、权限、事务)是“横切关注点”,装饰器能集中管理,代码更整洁。
  • 提高代码可读性和维护性
    业务逻辑与辅助功能分开,代码更易于理解和维护。
  • 符合开放封闭原则
    不修改已有函数代码,扩展功能,符合软件设计原则。

方面

说明

装饰器作用

给函数动态添加功能,增强代码复用性

主要用途

日志、权限、缓存、性能统计、事务管理

使用理由

避免重复代码,分离关注点,提高维护性

不使用影响

重复代码多,重复,难维护

推荐使用场景

多函数共享的辅助功能


三、装饰器如何使用

3.1 装饰器的基本语法

装饰器本质是一个函数,接受一个函数作为参数,返回一个新的函数。

def decorator(func):def wrapper():# 在调用原函数前可以做一些操作print("开始执行函数")func()# 在调用原函数后可以做一些操作print("函数执行完毕")return wrapper@decorator  # 语法糖,等同于 foo = decorator(foo)
def foo():print("这是被装饰的函数")foo()
"""
输出:
复制
开始执行函数
这是被装饰的函数
函数执行完毕
"""

装饰器的工作流程

  • @decorator 把被装饰函数 foo 传给 decorator 函数
  • decorator 返回一个新函数 wrapper
  • foo 被替换成了 wrapper
  • 调用 foo() 实际调用的是 wrapper(),在其中调用原始 foo

3.2 带参数的被装饰函数

如果被装饰函数有参数,装饰器的内部函数也要支持接收参数:

def decorator(func):def wrapper(*args, **kwargs):print("开始执行函数")result = func(*args, **kwargs)print("函数执行完毕")return resultreturn wrapper@decorator
def add(a, b):return a + bprint(add(3, 4))  # 输出 7

3.3 带参数的装饰器

有时装饰器本身也需要参数,这时需要三层嵌套:

def repeat(times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(times):func(*args, **kwargs)return wrapperreturn decorator@repeat(times=3)
def greet(name):print(f"Hello, {name}!")greet("Alice")
"""
输出:
Hello, Alice!
Hello, Alice!
Hello, Alice!
"""

3.4 链式装饰器(多个装饰器叠加)

def deco1(func):def wrapper():print("deco1 before")func()print("deco1 after")return wrapperdef deco2(func):def wrapper():print("deco2 before")func()print("deco2 after")return wrapper@deco1
@deco2
def foo():print("foo")foo()
"""
输出:
deco1 before
deco2 before
foo
deco2 after
deco1 after
"""

解释:

(1)装饰器应用顺序
Python中多个装饰器叠加时,装饰器的应用顺序是从下往上执行的。

  • foo 先被 deco2 装饰,变成 deco2(foo),返回一个新的函数(deco2 的 wrapper)。
  • 然后这个新函数又被 deco1 装饰,变成 deco1(deco2(foo)),返回最终包装后的函数(deco1 的 wrapper)。

(2)调用过程
当执行 foo() 时,实际上调用的是最外层装饰器 deco1 返回的 wrapper 函数。

(3)执行细节

  • deco1 的 wrapper 首先打印 "deco1 before"。
  • 然后调用它内部的 func(),此时 func 是 deco2(foo) 返回的 wrapper 函数。
  • 进入 deco2 的 wrapper,打印 "deco2 before"。
  • 调用原始的 foo() 函数,打印 "foo"。
  • deco2 的 wrapper 打印 "deco2 after",执行结束返回。
  • 回到 deco1 的 wrapper,打印 "deco1 after",执行结束返回。

(4)总结

  • 装饰器的嵌套调用使得输出顺序呈现“先外后内,后内先出”的特点。
  • 也就是说,装饰器的“before”部分按装饰器叠加顺序从外到内依次执行,“after”部分则按相反顺序执行。

四、易错点分析

4.1 装饰器不支持带参数的函数(忘写*args, **kwargs

错误示例:

def decorator(func):def wrapper():print("开始执行")func()  # 如果被装饰函数有参数,这里会报错return wrapper@decorator
def foo(name):print(f"Hello, {name}")foo("Alice")  # TypeError: wrapper() takes 0 positional arguments but 1 was given

正确写法:

def decorator(func):def wrapper(*args, **kwargs):print("开始执行")return func(*args, **kwargs)return wrapper

4.2 装饰器顺序错误导致逻辑混乱(链式装饰器)

多个装饰器叠加时,装饰器的应用顺序和调用顺序容易混淆。

  • 装饰器从下往上应用。
  • 调用时从最外层装饰器开始。

4.3 装饰器中忘记返回函数结果

包装函数如果没有返回被装饰函数的返回值,会导致调用者拿不到结果。

def decorator(func):def wrapper(*args, **kwargs):func(*args, **kwargs)  # 忘记returnreturn wrapper@decorator
def add(a, b):return a + bprint(add(1, 2))  # 输出 None,而不是 3

正确写法:

def decorator(func):def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapper

4.4 带参数装饰器忘记多层嵌套

带参数的装饰器必须三层函数嵌套,否则参数无法传递

http://www.xdnf.cn/news/18120.html

相关文章:

  • 三高架构杂谈
  • 软件定义汽车---创新与差异化之路
  • Jenkins全链路教程——Jenkins调用Maven构建项目
  • Kafka文件存储机制
  • 深入浅出决策树
  • (二十)深入了解 AVFoundation-编辑:使用 AVMutableVideoComposition 实现视频加水印与图层合成(下)——实战篇
  • Google 的 Opal:重新定义自动化的 AI 平台
  • Git版本控制与协作
  • 4.9 配置 开发服务器 和 请求代理
  • 汽车之家联合HarmonyOS SDK,深度构建鸿蒙生态体系
  • 使用Idea安装JDK
  • 从零开始,系统学习AI与机器学习:一份真诚的学习路线图
  • 容器化 Android 开发效率:cpolar 内网穿透服务优化远程协作流程
  • Baumer高防护相机如何通过YoloV8深度学习模型实现网球运动员和网球速度的检测分析(C#代码UI界面版)
  • WPF中BindingList<T>和List<T>
  • Conda技巧:修改Conda环境目录,节省系统盘空间
  • 学习:各种不同类型的for循环遍历,forEach/map/filter/every/some/includes/reduce的详细用法(1)
  • 【项目】分布式Json-RPC框架 - 项目介绍与前置知识准备
  • [Linux]学习笔记系列 --[mm][list_lru]
  • Redis-缓存-穿透-布隆过滤器
  • 测试Windows10IoT系统是否可以正常运行KingSCSDA3.8软件
  • Transformer架构的数学本质:从注意力机制到大模型时代的技术内核
  • 蓝凌EKP产品:JSP 性能优化和 JSTL/EL要点检查列表
  • Excel 表格数据自动填充
  • C语言私人学习笔记分享
  • Canny边缘检测
  • pyecharts可视化图表组合组件_Grid:打造专业数据仪表盘
  • python pandas库 series如何使用
  • 电脑上搭建HTTP服务器在局域网内其它客户端无法访问的解决方案
  • 【Tech Arch】Hive技术解析:大数据仓库的SQL桥梁