news 2026/4/3 13:52:12

Day 19:【99天精通Python】装饰器 - 给函数穿上“钢铁侠战衣“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day 19:【99天精通Python】装饰器 - 给函数穿上“钢铁侠战衣“

Day 19:【99天精通Python】装饰器 - 给函数穿上"钢铁侠战衣"

前言

欢迎来到第19天!

今天我们要学习 Python 中最优雅、最强大,但可能也是初学者最难理解的特性之一——装饰器 (Decorator)

你是否遇到过这样的场景:

  • 写了 100 个函数,现在需要给每个函数都加上"执行日志"(打印开始和结束时间)。
  • 写了一个 Web 后台,需要给某些页面加上"登录验证",只有登录了才能访问。

如果直接修改这 100 个函数的内部代码,工作量巨大且容易出错。装饰器允许我们在不修改原函数代码的前提下,动态地给函数增加新功能。这就好比给你的手机戴上了一个手机壳,手机还是那个手机,但它现在有了防摔的功能!

本节内容:

  • 函数作为参数传递
  • 闭包 (Closure) 简介
  • 装饰器的基本语法@
  • 万能装饰器 (支持参数)
  • functools.wraps的作用
  • 实战练习:计时器与权限验证

一、预备知识:函数的高级玩法

在 Python 中,函数是一等公民 (First-class Citizen)。这意味着:

  1. 函数可以被赋值给变量。
  2. 函数可以作为参数传递给另一个函数。
  3. 函数内部可以定义另一个函数。

1.1 函数作为参数

defsay_hello():print("Hello!")defrun_function(func):print("准备运行函数...")func()# 调用传入的函数print("函数运行结束。")run_function(say_hello)

1.2 闭包 (Closure)

闭包是指:在一个函数内部定义了一个内部函数,并且内部函数引用了外部函数的变量。

defouter():x=10definner():print(x)# 引用外部变量returninner# 返回内部函数本身fn=outer()fn()# 输出 10

二、装饰器入门:手写一个日志装饰器

假设我们要给函数eat()增加日志功能。

2.1 原始写法 (不推荐)

defeat():print("正在吃面条...")deflog_decorator(func):defwrapper():print("[Log] 开始执行...")func()# 执行原函数print("[Log] 执行完毕。")returnwrapper# 手动替换eat=log_decorator(eat)eat()

2.2 语法糖写法 (推荐)

Python 提供了@符号(语法糖),让上面的操作变得极其简洁。

deflog_decorator(func):defwrapper():print("[Log] 开始执行...")func()print("[Log] 执行完毕。")returnwrapper@log_decoratordefeat():print("正在吃面条...")@log_decoratordefsleep():print("正在睡觉...")# 直接调用eat()sleep()

运行结果

[Log] 开始执行... 正在吃面条... [Log] 执行完毕。 [Log] 开始执行... 正在睡觉... [Log] 执行完毕。

三、万能装饰器:支持参数与返回值

上面的装饰器有个缺陷:如果原函数带参数,或者有返回值,它就失效了。
为了通用,我们通常使用*args**kwargs,并显式return结果。

3.1 完整模板

defmy_decorator(func):defwrapper(*args,**kwargs):# 1. 在原函数之前做点什么print("--- Before ---")# 2. 执行原函数,并获取返回值result=func(*args,**kwargs)# 3. 在原函数之后做点什么print("--- After ---")# 4. 返回原函数的结果returnresultreturnwrapper

3.2 示例应用

@my_decoratordefadd(a,b):print(f"正在计算{a}+{b}")returna+b res=add(10,20)print(f"结果是:{res}")

运行结果

--- Before --- 正在计算 10 + 20 --- After --- 结果是: 30

四、保留元数据:functools.wraps

使用装饰器后,原函数的名字 (__name__) 和文档 (__doc__) 会变成wrapper函数的信息,这可能会导致调试困难。

解决方案:使用functools.wraps装饰wrapper

fromfunctoolsimportwrapsdeflog_decorator(func):@wraps(func)# <--- 加上这一行,保留原函数的元数据defwrapper(*args,**kwargs):"""我是Wrapper的文档"""print("Log...")returnfunc(*args,**kwargs)returnwrapper@log_decoratordefsearch():"""这是搜索函数"""print("Searching...")print(search.__name__)# 输出: search (如果不加 @wraps,会输出 wrapper)print(search.__doc__)# 输出: 这是搜索函数

五、实战练习

练习1:执行时间统计装饰器

编写一个@timer装饰器,计算任何函数的执行时间。

importtimefromfunctoolsimportwrapsdeftimer(func):@wraps(func)defwrapper(*args,**kwargs):start_time=time.time()result=func(*args,**kwargs)end_time=time.time()print(f"函数{func.__name__}耗时:{end_time-start_time:.4f}秒")returnresultreturnwrapper@timerdefheavy_calculation():time.sleep(1.5)# 模拟耗时操作return"Done"heavy_calculation()

练习2:简单的权限验证

模拟一个 web 系统,只有当全局变量user_logged_inTrue时,才能执行敏感操作。

user_logged_in=Falsedeflogin_required(func):@wraps(func)defwrapper(*args,**kwargs):ifnotuser_logged_in:print("错误:请先登录!")returnNonereturnfunc(*args,**kwargs)returnwrapper@login_requireddefdelete_database():print("数据库已删除!(危险操作)")# 未登录delete_database()# 登录user_logged_in=Truedelete_database()

六、常见问题

Q1:装饰器可以叠加吗?

可以。

@timer@login_requireddefwork():pass

执行顺序类似于"洋葱":从上到下包裹,执行时先外层逻辑,再内层逻辑

Q2:如何带参数的装饰器?

比如@repeat(3)表示重复执行3次。这需要三层嵌套函数(Wrap the wrapper)。

defrepeat(n):defdecorator(func):defwrapper(*args,**kwargs):for_inrange(n):func(*args,**kwargs)returnwrapperreturndecorator@repeat(3)defsay_hi():print("Hi!")

七、小结

装饰器 Decorator

本质

语法

应用场景

闭包 + 函数作为参数

不修改原代码增加功能

@decorator_name

wrapper(*args, **kwargs)

return func(...)

@wraps(func)

日志 Log

计时 Timer

权限 Auth

缓存 Cache

关键要点

  1. 装饰器是一个接受函数并返回新函数的高阶函数。
  2. @语法糖让代码更简洁。
  3. 编写装饰器时,务必使用*args**kwargs保证通用性。
  4. 记得使用@wraps保留原函数信息。

八、课后作业

  1. 重试机制:编写一个@retry装饰器。如果原函数抛出异常,自动重试最多 3 次。
  2. 输入检查:编写一个@check_int装饰器,确保函数接收到的所有参数都是整数。如果有非整数参数,打印错误并拒绝执行。
  3. HTML标签:编写一个装饰器工厂@tag("div"),可以将函数的返回值包裹在指定的 HTML 标签中。
    • 示例:返回 “hello” -><div>hello</div>

下节预告

Day 20:迭代器与生成器- 为什么 Python 处理大数据这么快且不占内存?揭秘yield关键字背后的流式处理机制。


系列导航

  • 上一篇:Day 18 - 常用内置模块
  • 下一篇:Day 20 - 迭代器与生成器(待更新)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/26 21:20:54

AI万能分类器实战教程:构建智能文档管理系统

AI万能分类器实战教程&#xff1a;构建智能文档管理系统 1. 引言 在当今信息爆炸的时代&#xff0c;企业每天都会产生海量的非结构化文本数据——从客户工单、用户反馈到内部文档和社交媒体评论。如何高效地对这些内容进行归类与管理&#xff0c;已成为提升运营效率的关键挑战…

作者头像 李华
网站建设 2026/4/3 2:48:34

茅台自动预约系统终极指南:从零开始快速部署

茅台自动预约系统终极指南&#xff1a;从零开始快速部署 【免费下载链接】campus-imaotai i茅台app自动预约&#xff0c;每日自动预约&#xff0c;支持docker一键部署 项目地址: https://gitcode.com/GitHub_Trending/ca/campus-imaotai 还在为每天手动抢购茅台而苦恼吗…

作者头像 李华
网站建设 2026/4/3 4:49:17

番茄小说下载器完整使用教程:轻松保存全网小说

番茄小说下载器完整使用教程&#xff1a;轻松保存全网小说 【免费下载链接】fanqienovel-downloader 下载番茄小说 项目地址: https://gitcode.com/gh_mirrors/fa/fanqienovel-downloader 想要永久收藏番茄小说平台上的精彩作品吗&#xff1f;这款免费开源的番茄小说下载…

作者头像 李华
网站建设 2026/3/27 5:23:14

LED阵列汉字显示实验:驱动芯片(如74HC595)应用解析

用74HC595点亮汉字&#xff1a;从移位寄存器到动态扫描的实战解析你有没有试过用单片机直接驱动一个1616的LED点阵&#xff1f;如果只靠MCU的GPIO口&#xff0c;你会发现——还没开始写代码&#xff0c;I/O资源就已经“红了”。更别提每个LED亮起时带来的电流冲击。这就像想用一…

作者头像 李华
网站建设 2026/4/2 11:19:08

Video2X视频无损放大完全指南:从新手到专家的完整教程

Video2X视频无损放大完全指南&#xff1a;从新手到专家的完整教程 【免费下载链接】video2x A lossless video/GIF/image upscaler achieved with waifu2x, Anime4K, SRMD and RealSR. Started in Hack the Valley II, 2018. 项目地址: https://gitcode.com/gh_mirrors/vi/vi…

作者头像 李华
网站建设 2026/3/22 6:26:02

番茄小说下载器完整使用指南:离线阅读新体验

番茄小说下载器完整使用指南&#xff1a;离线阅读新体验 【免费下载链接】fanqienovel-downloader 下载番茄小说 项目地址: https://gitcode.com/gh_mirrors/fa/fanqienovel-downloader 想要将心爱的番茄小说永久保存到本地设备吗&#xff1f;这款功能强大的番茄小说下载…

作者头像 李华