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

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.lryc.cn/news/624765.html

相关文章:

  • Kafka 零拷贝(Zero-Copy)技术详解
  • C++面试中的手写快速排序:从基础到最优的完整思考过程
  • IEC EN 62040 不间断电源系统(UPS)安全要求标准
  • 【音视频】芯片、方案、市场信息收集
  • 恒创科技:日本服务器 ping 不通?从排查到解决的实用指南
  • 政策技术双轮驱动智慧灯杆市场扩容,塔能科技破解行业痛点
  • 【轨物交流】轨物科技与华为鲲鹏生态深度合作 光伏清洁机器人解决方案获技术认证!
  • 微算法科技(NASDAQ: MLGO)研究分片技术:重塑区块链可扩展性新范式
  • 【P38 6】OpenCV Python——图片的运算(算术运算、逻辑运算)加法add、subtract减法、乘法multiply、除法divide
  • Maven resources资源配置详解
  • 深度研究系统、方法与应用的综述
  • kubeadm方式部署k8s集群
  • zsh 使用笔记 命令行智能提示 bash智能
  • 视频因为264问题无法网页播放,解决方案之一:转化视频
  • 【matlab】考虑源荷不平衡的微电网鲁棒定价研究
  • 第7节 神经网络
  • grep命令要点、详解和示例
  • 淘宝扭蛋机小程序开发:引领电商娱乐化新潮流
  • 剧本杀小程序系统开发:保障游戏公平,营造健康娱乐环境
  • 香港数据合集:建筑物、手机基站、POI、职住数据、用地类型
  • 27.Linux 使用yum安装lamp,部署wordpress
  • 【CV 目标检测】Fast RCNN模型③——模型训练/预测
  • 短剧小程序系统开发:推动短剧行业规范化与标准化发展
  • 移动端PFD预览组件Vue3(非插件)
  • Nacos-6--Naco的QUIC协议实现高可用的工作原理
  • Linux系统启动原理及故障排除
  • GitHub Actions 从核心思想到最佳实践
  • Go语言基础结构全解析
  • 海洋牧场:奏响乡村振兴的蓝色乐章
  • Mysql——前模糊索引失效原因及解决方式