Python 装饰器:我以为的语法糖,其实是定义时立刻执行的函数调用
这个坑我是被项目逼着学会的。有一次产品要求给所有接口加耗时统计我打开函数开头写一行start_time time.time()return前再写一行print。改到第三个函数时我开始怀疑人生——同样的代码复制粘贴几十遍以后改格式还得逐个文件翻。于是搜索Python 不修改函数添加日志所有答案都指向同一个词装饰器。但学装饰器的过程比改代码还痛苦。教程上来就甩一个xxx看不懂为什么后面跟函数名、为什么有的装饰器带括号有的不带、为什么 wrapper 里要写*args, **kwargs、为什么还要 import 一个叫wraps的东西。我一度把它当成需要背下来的语法咒语。直到后来才明白装饰器没有任何魔法它只是一个普通的函数调用只是 Python 官方提供的一个缩写。把这层窗户纸捅破所有为什么就都有了答案。这篇笔记就是从头捅到底。一、先补两块地基函数是对象闭包是记忆装饰器踩坑的根源是很多人跳过了两个基础概念直接学语法。快速补齐。第一块函数是对象。Python 里函数和字符串、整数一样是普通值——可以赋给变量、装进列表、当参数传给另一个函数、当返回值返回defhello(name):returnfHello,{name}greethello# 函数赋给变量注意没有括号——不是调用是传递funcs[hello,greet]# 装进列表print(funcs[0](Bob))# Hello, Bobdefcall_it(func,arg):# 函数当参数returnfunc(arg)print(call_it(hello,Carol))hello和hello()的区别是理解一切后续内容的钥匙前者是函数对象本身后者是执行它并取结果。第二块闭包。函数里定义函数内层函数能访问外层函数的变量——而且即使外层函数已经 return 了这个记忆依然保留defouter(x):definner(y):returnxy# inner 记住了 xreturninner add5outer(5)# outer 执行完毕、已经返回print(add5(10))# 15 —— x5 还活着可以把闭包理解成函数对象随身带一个小背包里面装着它定义时所在作用域的变量引用。这个小背包正是装饰器能记住原函数的根本。两块地基铺好装饰器只剩一件事要做把函数传进函数、包装、返回。二、撕开它就是一行赋值语句先完全不用语法糖手写一个包装defadd_log(func):defwrapper():print(函数执行前)func()print(函数执行后)returnwrapperdefsay_hello():print(Hello!)# 手动包装传进去拿回新的替换原名say_helloadd_log(say_hello)say_hello()# 输出函数执行前 / Hello! / 函数执行后看最后那行赋值say_hello add_log(say_hello)。这个动作就是装饰器的全部——把原函数传给包装函数返回增强版用原名指回它。而 Python 觉得这行赋值写起来啰嗦就发明了缩写add_logdefsay_hello():print(Hello!)这两段代码在 Python 解释器眼里一模一样。遇到add_log解释器的翻译动作就是执行say_hello add_log(say_hello)。这是全文最重要的一句话值得单独放大不是注解或标记它是一条在函数定义完成的瞬间立刻执行的语句。带括号就立即执行——这是函数调用的基本规则不会豁免它。这个认知直接解释了新手最容易栽的坑第五节会回来收割。三、造一个万能模板*args、**kwargs和wraps上一节的wrapper()只能包装无参数无返回值的函数——真实代码几乎不存在这种函数。需要两个补丁补完就是一个可以无脑复用的模板。补丁一适配任意签名。让 wrapper 接收任意位置参数和关键字参数原封不动转发给原函数defwrapper(*args,**kwargs):resultfunc(*args,**kwargs)# 转发并接住返回值returnresult补丁二治身份失忆症。包装之后say_hello这个名字指向的其实是wrapper——于是say_hello.__name__变成了wrapper文档字符串也丢了。这会破坏调试信息更严重的是让依赖函数名做路由注册的框架Flask、FastAPI直接失效。解药是标准库的functools.wraps它把原函数的元数据__name__、__doc__等拷贝给 wrapperfromfunctoolsimportwrapsdefadd_log(func):wraps(func)# 一行身份找回defwrapper(*args,**kwargs):print(f调用{func.__name__})resultfunc(*args,**kwargs)print(f{func.__name__}返回{result!r})returnresultreturnwrapper两块补丁打完这就是通用装饰器的标准模板——计时、重试、缓存、权限校验全都长这个骨架。四、带参数的装饰器为什么要套三层下一个拦路虎是这种写法repeat(num_times3)defgreet(name):print(fHello,{name}!)repeat后面怎么带括号了之前的add_log不带啊回到那个核心认知后面跟的是一个会返回装饰器的调用。拆解执行过程repeat(num_times3)defgreet(name):...等价于greetrepeat(num_times3)(greet)从里往外读先执行repeat(num_times3)它返回一个装饰器这个装饰器再吃掉greet返回 wrapper。所以repeat的结构必须是三层嵌套defrepeat(num_times):# 第一层装饰器工厂吃配置defdecorator(func):# 第二层真正的装饰器吃函数wraps(func)defwrapper(*args,**kwargs):# 第三层干活的人for_inrange(num_times):resultfunc(*args,**kwargs)returnresultreturnwrapperreturndecorator为什么必然三层因为配置和函数是两次分开的投喂num_times在repeat(num_times3)时进来func在函数定义完成时才进来。两次投喂需要两层函数分别接住加上最里面干活的 wrapper正好三层。这不是设计炫技是逻辑推论——想通了这一点带参装饰器要套三层就从需要背的规则变成了显然的事实。顺带补一条堆叠规则多个装饰器从下往上应用等价于从上往下嵌套包裹abdeff():...# 等价于 f a(b(f))五、三大踩坑实录学完结构该收割第二节埋下的那个认知了。三个坑都源于对是语句理解不彻底。坑一装饰器在导入时就偷跑杀伤力最大我在生产环境栽过的具体场景为了发监控日志装饰器里初始化了一个数据库连接池。结果容器启动后 K8s 健康检查超时直接杀 Pod——明明一个接口都没被调用启动却卡在 import 某个模块那里两分钟。还原现场importtimedefmonitor_decorator(func):print(f正在初始化监控绑定{func.__name__})time.sleep(3)# 模拟连接池初始化print(监控就绪)defwrapper(*args,**kwargs):returnfunc(*args,**kwargs)returnwrappermonitor_decoratordefget_user_info(user_id):returnf用户{user_id}只是 import 这个模块、一次都没调用控制台照样输出那两行、照样等 3 秒。因为monitor_decorator就是get_user_info monitor_decorator(get_user_info)——只要模块被加载这行就执行装饰器外层的代码就跑完。我当年以为它在等指令其实它第一眼看到我就动手了。正确姿势所有重操作连数据库、读文件、建连接必须挪进 wrapper 内部实现懒加载——第一次真正调用时才初始化。装饰器外层只做轻量的准备工作。坑二忘了wraps框架炸了代码本地跑得好好的接上 Flask/FastAPI 后路由全失效。排查半天发现__name__全是wrapper——框架靠函数名注册路由包装后名字丢了。习惯写 wrapper 之前先敲wraps(func)把它当成模板的一部分而不是可选项。坑三三层漏写一层报错看不懂带参数装饰器少嵌套一层repeat(3)会把3当函数传进去报错完全对不上号。定位办法就是心里默背那句等价式f repeat(3)(f)检查每一层吃的是什么。六、三个可以直接抄走的实战装饰器学习笔记的终点是工具箱。三个高频场景的成品代码均已含wraps、均已懒加载。计时器importtimefromfunctoolsimportwrapsdeftiming(func):wraps(func)defwrapper(*args,**kwargs):starttime.perf_counter()resultfunc(*args,**kwargs)elapsedtime.perf_counter()-startprint(f{func.__name__}:{elapsed:.4f}s)returnresultreturnwrapper重试器带参数版defretry(max_attempts3,delay1):defdecorator(func):wraps(func)defwrapper(*args,**kwargs):forattemptinrange(max_attempts):try:returnfunc(*args,**kwargs)exceptExceptionase:ifattemptmax_attempts-1:raisetime.sleep(delay*(2**attempt))# 指数退避returnwrapperreturndecoratorretry(max_attempts3,delay0.5)deffetch_api(url):...缓存器——其实标准库已经写好了functools.cache就是一个官方装饰器直接用fromfunctoolsimportcachecachedeffibonacci(n):returnnifn2elsefibonacci(n-1)fibonacci(n-2)看到cache时可以顺便检验一下自己的理解它是无参数装饰器两层结构在定义时执行wrapper 里带着对原函数的闭包引用。结语回头看这篇笔记走过的路其实只有一句话不是魔法是f decorator(f)这行赋值的官方缩写——它在函数定义完成的瞬间执行仅此而已。这句话往前推得到装饰器在导入时就运行于是坑一的启动卡死可以推演出来往前推得到带参数时后面是一次调用返回装饰器于是三层嵌套的必然性可以推演出来往前推得到原名被 wrapper 顶替于是wraps的必要性也可以推演出来。学语法的痛苦九成来自把可推导的东西当需要背诵的东西。这也是技术学习笔记这个方向的价值所在踩坑的意义从来不是记住坑的位置而是把坑背后的机制挖出来。机制有了下一个没见过的语法糖——property、staticmethod、dataclass——你都能自己拆开看。

相关新闻

《信息论基础》第一章学习笔记

《信息论基础》第一章学习笔记

信息论来源:1948年香农发表论文《通信的数学理论》,在噪声信道中有效传输信息,对信息给予科学定量描述,创造性地采用概率论方法研究通信问题,给出信息定量描述,奠定经典信息论基础。1949年香农发表《噪声下…

2026/10/12 2:29:25 阅读更多 →
运动粘度仪与全自动低温乌氏粘度仪:原理、应用与选型

运动粘度仪与全自动低温乌氏粘度仪:原理、应用与选型

搞油品、高分子、绝缘油检测这一行的人,应该都有过这种经历:一排玻璃乌氏管泡在透明恒温油浴里,人蹲在边上掐着秒表,眼睛死死盯着液面流过上下刻度线,一整天下来眼睛都快花了。后来换了全自动运动粘度仪,从…

2026/10/12 2:29:25 阅读更多 →
ISO9001与 ISO14001/45001 一起做三体系,企业能省多少?整合认证全攻略

ISO9001与 ISO14001/45001 一起做三体系,企业能省多少?整合认证全攻略

摘要:管理体系认证中,质量、环境、职业健康安全三体系常被企业一并提上日程。本文围绕 ISO9000(即 ISO 9001)、ISO 14001、ISO 45001 的整合认证,客观梳理三体系差异、整合方式与节省逻辑,帮助宁夏企业理性…

2026/10/12 2:29:25 阅读更多 →

最新新闻

PLC基本指令详解:从位逻辑到定时计数,掌握梯形图编程核心

PLC基本指令详解:从位逻辑到定时计数,掌握梯形图编程核心

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

2026/10/12 3:19:56 阅读更多 →
柔性上料盘如何替代振动盘:从换型瓶颈到视觉引导的产线升级指南

柔性上料盘如何替代振动盘:从换型瓶颈到视觉引导的产线升级指南

简介:《全球与中国柔性上料盘市场现状及未来发展趋势(2024版)》是一份QYResearch出品的专业市场研究报告,面向柔性上料与自动化产线设备从业者、工业机器人厂商、市场分析师及投资研究人员。报告以2019至2023年为历史期、2024至20…

2026/10/12 3:19:56 阅读更多 →
WiFi分析工具设计实战:从数据采集到信道优化与故障排查

WiFi分析工具设计实战:从数据采集到信道优化与故障排查

1. 从一个标题说起:这个工具到底在解决什么问题第一次看到“Jev powered WiFi analysis tool”这个标题,我的直觉是:这大概率是一个把无线网络分析能力封装成轻量级工具的项目,名字里的“Jev”可能是作者自定的代号、模块名或者某…

2026/10/12 3:19:56 阅读更多 →
SpringBoot+Vue美食网站系统:前后端分离全栈项目架构与部署详解

SpringBoot+Vue美食网站系统:前后端分离全栈项目架构与部署详解

做个人项目这些年,前后端分离的练手项目做了不少,但每次有人让我推荐一个既能完整跑起来、又能覆盖主流开发流程的学习项目,我第一反应往往是这套美食网站系统。为什么?因为它的技术选型非常贴近当下中小型项目的真实组合&#xf…

2026/10/12 3:19:56 阅读更多 →
高斯赛德尔迭代法:大规模稀疏线性方程组的工程解法与实战技巧

高斯赛德尔迭代法:大规模稀疏线性方程组的工程解法与实战技巧

线性方程组这东西,刚接触数值计算的时候,总觉得不是事——高斯消元一把梭,n100也就是眨眨眼的事。可等你真在工程里碰到几十万未知量、矩阵非零元稀稀落落排成带状或块状的时候,直接法的“快”就变成了一种幻觉:要么内…

2026/10/12 3:19:56 阅读更多 →
attrs 比较机制完全指南:默认相等性、排序生成与自定义比较(Comparison)

attrs 比较机制完全指南:默认相等性、排序生成与自定义比较(Comparison)

后端 【免费下载链接】attrs Python Classes Without Boilerplate 项目地址: https://gitcode.com/gh_mirrors/at/attrs 点击查看 免费下载 本文围绕 attrs 官方文档 docs/comparison.md 展开,系统讲解 attrs 类实例的相等性(equality&#…

2026/10/12 3:18:56 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/11 14:36:54 阅读更多 →