news 2026/9/4 17:26:03

python-第24天:Python中的装饰器详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
python-第24天:Python中的装饰器详解

30天入门Python(基础篇)——第24天:Python中的装饰器详解

📅学习日期:第24天 | ⏱️预计用时:60分钟 | 📊难度等级:⭐⭐⭐⭐


🎯 学习目标

  • 理解装饰器的本质和作用
  • 掌握装饰器的基本语法和使用方法
  • 学会编写带参数的装饰器
  • 理解functools.wraps的作用
  • 掌握类装饰器的使用方法
  • 能够编写多个装饰器叠加的复杂场景

一、什么是装饰器?

1.1 装饰器的概念

装饰器(Decorator)是 Python 中一个非常强大且优雅的特性。从本质上讲,装饰器就是一个函数,它接受一个函数作为参数,返回一个新的函数。装饰器允许我们在不修改原有函数代码的情况下,为其添加额外的功能。

打个比方:装饰器就像给手机套上一个手机壳。手机本身的功能不变,但手机壳给它增加了防摔、美观等额外特性。

1.2 装饰器的设计原则

装饰器遵循开放-封闭原则(Open-Closed Principle)

  • 对扩展开放:可以给函数添加新功能
  • 对修改封闭:不需要修改原函数的代码

二、函数装饰器的基础

2.1 最简单的装饰器

defmy_decorator(func):"""一个简单的装饰器"""defwrapper():print("在函数执行之前")func()print("在函数执行之后")returnwrapper@my_decoratordefsay_hello():print("Hello!")# 调用被装饰的函数say_hello()

输出:

在函数执行之前 Hello! 在函数执行之后

2.2 装饰器的执行流程

@my_decorator def say_hello(): ...

等价于:

defsay_hello():...say_hello=my_decorator(say_hello)

2.3 原理解析

让我们逐步拆解上面的装饰器:

  1. 定义阶段:Python 解释器读到@my_decorator时,会立即调用my_decorator(say_hello),并将返回值重新赋值给say_hello
  2. 调用阶段:当我们调用say_hello()时,实际调用的是wrapper()函数
  3. 执行阶段wrapper()内部调用了原始的func()(即原say_hello),并在其前后添加了额外逻辑

三、带参数的函数装饰器

3.1 装饰带参数的函数

上面的装饰器只能装饰无参数的函数。实际开发中,我们需要处理带参数的函数:

defmy_decorator(func):defwrapper(*args,**kwargs):print(f"调用函数{func.__name__},参数:{args},{kwargs}")result=func(*args,**kwargs)print(f"函数{func.__name__}执行完毕,返回值:{result}")returnresultreturnwrapper@my_decoratordefadd(a,b):returna+b@my_decoratordefgreet(name,age=20):print(f"你好,{name}! 你今年{age}岁。")# 测试add(3,5)greet("小明",age=25)

输出:

调用函数 add,参数: (3, 5), {} 函数 add 执行完毕,返回值: 8 调用函数 greet,参数: ('小明',), {'age': 25} 你好, 小明! 你今年 25 岁。 函数 greet 执行完毕,返回值: None

3.2 关键点:*args**kwargs

  • *args:接收所有位置参数,打包成元组
  • **kwargs:接收所有关键字参数,打包成字典
  • 这种写法使装饰器可以适配任意参数签名的函数

四、带参数的装饰器

有时候我们需要给装饰器本身传递参数,比如指定重试次数、日志级别等。

4.1 语法结构

带参数的装饰器需要三层嵌套

defrepeat(times):"""装饰器工厂:控制函数重复执行次数"""defdecorator(func):defwrapper(*args,**kwargs):foriinrange(times):print(f"第{i+1}次执行:")result=func(*args,**kwargs)returnresultreturnwrapperreturndecorator@repeat(3)defsay_hi():print("Hi!")say_hi()

输出:

第 1 次执行: Hi! 第 2 次执行: Hi! 第 3 次执行: Hi!

4.2 执行流程解析

@repeat(3)defsay_hi():...

等价于:

defsay_hi():...say_hi=repeat(3)(say_hi)# ↑ 先执行 ↑ 再执行
  1. repeat(3)先执行,返回decorator函数
  2. decorator(say_hi)再执行,返回wrapper函数
  3. 最终say_hi指向wrapper

4.3 实战:带参数的重试装饰器

importtimedefretry(max_retries=3,delay=1):"""重试装饰器:函数失败时自动重试"""defdecorator(func):defwrapper(*args,**kwargs):forattemptinrange(max_retries):try:returnfunc(*args,**kwargs)exceptExceptionase:print(f"第{attempt+1}次尝试失败:{e}")ifattempt<max_retries-1:print(f"等待{delay}秒后重试...")time.sleep(delay)print("所有重试均失败")returnwrapperreturndecorator# 模拟一个可能失败的函数importrandom@retry(max_retries=3,delay=0.5)deffetch_data():ifrandom.random()<0.7:# 70% 概率失败raiseConnectionError("网络连接失败")return"数据获取成功!"result=fetch_data()

五、functools.wraps的作用

5.1 问题引入

使用装饰器后,原函数的元信息(函数名、文档字符串等)会丢失:

defmy_decorator(func):defwrapper(*args,**kwargs):"""wrapper 的文档字符串"""returnfunc(*args,**kwargs)returnwrapper@my_decoratordefgreet(name):"""向用户打招呼"""print(f"Hello,{name}!")print(f"函数名:{greet.__name__}")# 输出: wrapperprint(f"文档字符串:{greet.__doc__}")# 输出: wrapper 的文档字符串

5.2 解决方案:@wraps

fromfunctoolsimportwrapsdefmy_decorator(func):@wraps(func)# ← 关键:保留原函数的元信息defwrapper(*args,**kwargs):"""wrapper 的文档字符串"""returnfunc(*args,**kwargs)returnwrapper@my_decoratordefgreet(name):"""向用户打招呼"""print(f"Hello,{name}!")print(f"函数名:{greet.__name__}")# 输出: greetprint(f"文档字符串:{greet.__doc__}")# 输出: 向用户打招呼

5.3 最佳实践

所有装饰器都应该使用@wraps,这是一个良好的编程习惯。它能:

  • 保留原函数名__name__
  • 保留原函数文档__doc__
  • 保留原函数的其他元信息
  • 对调试、日志、自省等场景非常重要

六、多个装饰器的叠加

6.1 装饰器链

一个函数可以同时被多个装饰器装饰:

defdecorator_a(func):@wraps(func)defwrapper(*args,**kwargs):print("[A] 执行前")result=func(*args,**kwargs)print("[A] 执行后")returnresultreturnwrapperdefdecorator_b(func):@wraps(func)defwrapper(*args,**kwargs):print("[B] 执行前")result=func(*args,**kwargs)print("[B] 执行后")returnresultreturnwrapper@decorator_a@decorator_bdefhello():print("Hello World!")hello()

输出:

[A] 执行前 [B] 执行前 Hello World! [B] 执行后 [A] 执行后

6.2 执行顺序

装饰器的执行顺序是从内到外(从下到上):

@decorator_a# 最外层,最后执行@decorator_b# 内层,先执行deffunc():...

等价于:

func=decorator_a(decorator_b(func))

七、类装饰器

装饰器不仅可以是函数,也可以是类。

7.1 类作为装饰器

classCounter:"""统计函数调用次数的装饰器"""def__init__(self,func):self.func=func self.count=0def__call__(self,*args,**kwargs):self.count+=1print(f"第{self.count}次调用")returnself.func(*args,**kwargs)@Counterdefsay_hello():print("Hello!")say_hello()say_hello()say_hello()print(f"总共调用了{say_hello.count}次")

输出:

第 1 次调用 Hello! 第 2 次调用 Hello! 第 3 次调用 Hello! 总共调用了 3 次

7.2 关键点

  • 类作为装饰器时,需要实现__call__方法
  • __init__接收被装饰的函数
  • __call__在每次调用函数时执行

八、实战案例

8.1 计时装饰器

importtimefromfunctoolsimportwrapsdeftimer(func):"""计算函数执行时间的装饰器"""@wraps(func)defwrapper(*args,**kwargs):start=time.time()result=func(*args,**kwargs)end=time.time()print(f"{func.__name__}执行耗时:{end-start:.6f}秒")returnresultreturnwrapper@timerdefslow_function():"""模拟一个耗时操作"""time.sleep(1)return"完成"slow_function()

8.2 日志装饰器

importloggingfromfunctoolsimportwraps logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(message)s')deflog(func):"""记录函数调用的日志装饰器"""@wraps(func)defwrapper(*args,**kwargs):logging.info(f"调用{func.__name__}({', '.join(map(str,args))},{kwargs})")result=func(*args,**kwargs)logging.info(f"{func.__name__}返回{result}")returnresultreturnwrapper@logdefmultiply(a,b):returna*b multiply(6,7)

8.3 缓存装饰器

fromfunctoolsimportwrapsdefcache(func):"""简单的缓存装饰器"""cache_dict={}@wraps(func)defwrapper(*args):ifargsincache_dict:print(f"从缓存中获取:{args}")returncache_dict[args]result=func(*args)cache_dict[args]=resultprint(f"计算并缓存:{args}")returnresultreturnwrapper@cachedeffibonacci(n):"""计算斐波那契数列"""ifn<=1:returnnreturnfibonacci(n-1)+fibonacci(n-2)print(fibonacci(5))

九、常见错误和注意事项

9.1 忘记返回 wrapper

# ❌ 错误:没有返回 wrapperdefbad_decorator(func):defwrapper(*args,**kwargs):print("before")func(*args,**kwargs)print("after")# 这里缺少 return wrapper# ✅ 正确defgood_decorator(func):defwrapper(*args,**kwargs):print("before")returnfunc(*args,**kwargs)returnwrapper# ← 必须返回

9.2 忘记返回函数结果

# ❌ 错误:没有返回 resultdefwrapper(*args,**kwargs):func(*args,**kwargs)# 返回值被丢弃# ✅ 正确defwrapper(*args,**kwargs):returnfunc(*args,**kwargs)# 保留返回值

9.3 类方法装饰器中的 self

装饰类方法时,第一个参数是self,要注意:

defmethod_logger(func):@wraps(func)defwrapper(self,*args,**kwargs):print(f"调用{self.__class__.__name__}.{func.__name__}")returnfunc(self,*args,**kwargs)returnwrapper

十、总结

知识点关键内容
装饰器本质接受函数作为参数,返回新函数的高阶函数
基本语法@decorator放在函数定义上方
参数处理使用*args, **kwargs适配任意参数
装饰器参数需要三层嵌套函数
元信息保留使用@wraps(func)
多个装饰器从下到上(从内到外)执行
类装饰器实现__init____call__方法

核心要点回顾

  1. 装饰器 = 函数包装器,可以在不修改原函数的情况下添加功能
  2. @wraps是必备,保留函数名和文档字符串
  3. *args, **kwargs是标配,让装饰器可以装饰任何函数
  4. 三层嵌套是带参数装饰器的标准写法
  5. 多个装饰器叠加时,注意执行顺序是从下到上

十一、练习题

练习 1:编写权限检查装饰器

编写一个装饰器,检查用户是否有权限调用某函数。如果用户名不在允许列表中,打印 “无权限”。

练习 2:编写性能监控装饰器

编写一个装饰器,统计并打印函数的执行时间,如果超过指定阈值则发出警告。

练习 3:编写 HTML 标签装饰器

编写一个装饰器,将函数的字符串返回值包裹在指定的 HTML 标签中(如<p>,<div>)。

# 期望效果@html_tag("p")defget_text():return"Hello World"print(get_text())# 输出: <p>Hello World</p>

参考答案

练习 1 参考答案
fromfunctoolsimportwrapsdefrequire_permission(allowed_users):defdecorator(func):@wraps(func)defwrapper(user,*args,**kwargs):ifusernotinallowed_users:print(f"用户 '{user}' 无权限调用{func.__name__}")returnNonereturnfunc(user,*args,**kwargs)returnwrapperreturndecorator@require_permission(["admin","manager"])defdelete_record(user,record_id):print(f"用户{user}删除了记录{record_id}")delete_record("admin",101)delete_record("guest",102)
练习 2 参考答案
importtimefromfunctoolsimportwrapsdeftime_limit(max_seconds):defdecorator(func):@wraps(func)defwrapper(*args,**kwargs):start=time.time()result=func(*args,**kwargs)elapsed=time.time()-startifelapsed>max_seconds:print(f"⚠️ 警告:{func.__name__}耗时{elapsed:.2f}s,超过阈值{max_seconds}s")else:print(f"✅{func.__name__}耗时{elapsed:.4f}s")returnresultreturnwrapperreturndecorator
练习 3 参考答案
fromfunctoolsimportwrapsdefhtml_tag(tag_name):defdecorator(func):@wraps(func)defwrapper(*args,**kwargs):result=func(*args,**kwargs)returnf"<{tag_name}>{result}</{tag_name}>"returnwrapperreturndecorator@html_tag("p")defget_text():return"Hello World"print(get_text())# <p>Hello World</p>

🎉 恭喜你完成了第24天的学习!装饰器是 Python 中非常重要的高级特性,掌握它将让你的代码更加优雅和模块化。

📌下一天预告:第25天 —— Python中模块与包详解

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 17:22:49

盘点第二届世界人形机器人运动会的“乐子”与“门道”

【EI检索会议 | SPIE出版】 2026年智能计算与多模态信号处理国际学术会议&#xff08;CIMSP 2026&#xff09;-CSDN博客文章浏览阅读730次&#xff0c;点赞24次&#xff0c;收藏6次。2026年智能计算与多模态信号处理国际学术会议&#xff08;CIMSP2026&#xff09;将于8月21-23…

作者头像 李华
网站建设 2026/9/4 17:21:59

STM32+MPU6050滤波实战:从裸数据到互补滤波姿态解算

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 17:18:51

专插本公办 vs 民办区别!学费、难度、含金量全方位对比

本文由育教大师专插本整理&#xff0c;仅供学生参考学习广东专插本录取分为公办本科和民办本科两大类型&#xff0c;两者毕业证书性质、学信网学历身份一致&#xff0c;均属于全日制统招本科&#xff0c;国家认可、学信网可查&#xff0c;可正常用于考研、考公、考证与求职。核…

作者头像 李华
网站建设 2026/9/4 17:17:46

基于SpringBoot的校园失物招领系统(毕设源码+文档)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/4 17:16:38

多模态与实时交互:语音 Agent 与 Computer Use 的架构分析

多模态与实时交互&#xff1a;语音 Agent 与 Computer Use 的架构分析 前几章构建的 Agent 与世界的交互是轮流的&#xff1a;用户说完一句&#xff0c;Agent 想一段、调用几个工具&#xff0c;再回一句&#xff1b;在它思考的这段时间里&#xff0c;世界被默认为静止的。这个前…

作者头像 李华