当前位置: 首页 > 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.dtcms.com/a/337279.html

相关文章:

  • c++最长上升子序列长度
  • 雷卯针对香橙派Orange Pi 5 Plus开发板防雷防静电方案
  • JavaWeb 请求与响应乱码问题全面解决方案
  • React diff——差异协调算法简介
  • 算法-决策树
  • 从决策树基础到熵与信息增益
  • 网络间的通用语言TCP/IP-网络中的通用规则1
  • 本地文件上传到gitee仓库的详细步骤
  • sem_post函数的定义及作用
  • 【81页PPT】国内某知名大型制药企业制药数字化转型项目汇报方案(附下载方式)
  • 无人设备遥控器之操控信号精度篇
  • 【68页PPT】MES系统数字化工厂解决方案(附下载方式)
  • 深入剖析以太坊虚拟机(EVM):区块链世界的计算引擎
  • go 多版本共存【goup + alias方案】
  • React diff Vue diff介绍
  • 【2025CVPR-目标检测方向】RaCFormer:通过基于查询的雷达-相机融合实现高质量的 3D 目标检测
  • 牛子图论进阶
  • TEST_
  • Linux系统启动原理及故障排除
  • 场外个股期权的行权日是t+多少个交易日?
  • 【牛客刷题】最大公约数与最小公倍数:算法详解与实现
  • linux中的hostpath卷与nfs卷以及静态持久卷的区别
  • JAiRouter 架构揭秘:一个面向 AI 时代的响应式网关设计
  • Flutter 自定义 Switch 切换组件完全指南
  • 技术经典场景之协议转换
  • IR-CUT与CCD CMOS的关系
  • 国标:开展环境卫生满意度调查
  • 【P18 3-10】OpenCV Python—— 鼠标控制,鼠标回调函数(鼠标移动、按下、。。。),鼠标绘制基本图形(直线、圆、矩形)
  • 【笔记】和各大AI大语言模型合作写项目—slirp.go
  • 之前说的要写的TCP高性能服务器,今天来了