Python装饰器实战:从闭包原理到数据管道里的4个实用装饰器
文章目录环境信息前言同样的代码有人写成一百行有人写成一行装饰器一、装饰器原理为什么一个 就能包住函数二、坑为什么装饰后函数名变了三、带参数的装饰器三层嵌套四、数据管道里的4个实用装饰器装饰器1retry —— 爬虫容错装饰器2timing —— 性能分析装饰器3cache —— API缓存装饰器4validate —— 数据校验复用第1篇的正则五、装饰器栈多个装饰器叠加六、总结环境信息项目版本/说明Python3.10标准库functools / time / functools.lru_cache依赖无需第三方库场景数据管道爬虫容错 / 性能分析 / API缓存 / 数据校验前言同样的代码有人写成一百行有人写成一行装饰器前两天我在重构自己的香港数据管道脚本。里面有一段逻辑是这样的——每次调政府API都要包一层try/except做重试每个函数开头都要start time.time()计时每段校验逻辑都要重复调用。写到第五个函数的时候我意识到我在反复写一模一样的代码只是套在不同的函数外面。这正好是装饰器的用武之地。但很多人对装饰器的理解停在就是那个 符号。这导致两个问题一是抄别人的装饰器改个参数就报错二是写不出带参数的装饰器那种三层嵌套的。这篇文章想把这两层都讲透——先讲清装饰器为什么能工作闭包原理再给4个数据管道里真实会用的装饰器。收藏提示①装饰器的本质是高阶函数——函数可以作为参数传入也可以作为返回值返回。理解了这一句话就理解了80%的装饰器。文末有4个开箱即用的装饰器。一、装饰器原理为什么一个 就能包住函数先看最朴素的装饰器——不用 语法手动包一层defmy_func(x):returnx*2# 装饰器就是一个接收函数、返回函数的高阶函数deftimer(func):importtimedefwrapper(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)print(f{func.__name__}耗时{time.time()-start:.4f}秒)returnresultreturnwrapper# 注意返回的是 wrapper不是调用结果# 手动装饰my_functimer(my_func)print(my_func(21))# 会先打印耗时再返回 42关键点就在最后一行my_func timer(my_func)。我们把my_func这个函数传给了timer然后把timer返回的新函数wrapper重新赋值给my_func。从此my_func这个变量名指向的是wrapper了。语法只是这个赋值操作的语法糖timerdefmy_func(x):returnx*2# 完全等价于# my_func timer(my_func)而wrapper能记住外层函数func也就是原来的 my_func靠的是闭包——内层函数引用了外层函数的变量即使外层函数已经返回这个变量也不会被销毁。收藏提示②装饰器 高阶函数 闭包。timer就是func timer(func)的简写。理解了这一点后面带参装饰器的三层嵌套就顺理成章了。二、坑为什么装饰后函数名变了新手最容易踩的一个坑——装饰之后函数的元信息丢了deftimer(func):importtimedefwrapper(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)print(f耗时{time.time()-start:.4f}秒)returnresultreturnwrappertimerdefmy_func(x):returnx*2print(my_func.__name__)# 输出 wrapper而不是 my_funcprint(my_func.__doc__)# None原始docstring丢了函数名变成了wrapper。这在调试、日志、序列化时会造成混乱。修复方式是用functools.wrapsfromfunctoolsimportwrapsimporttimedeftimer(func):wraps(func)# 关键保留原函数的元信息defwrapper(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)print(f耗时{time.time()-start:.4f}秒)returnresultreturnwrappertimerdefmy_func(x):这是原始文档字符串returnx*2print(my_func.__name__)# my_func ✓print(my_func.__doc__)# 这是原始文档字符串 ✓规则只要写装饰器就必须加wraps(func)。没有例外。三、带参数的装饰器三层嵌套前面讲的装饰器是两层嵌套timer 里套 wrapper。但如果我想让装饰器接受参数——比如retry(times3)而不是固定重试次数——就需要三层嵌套fromfunctoolsimportwrapsimporttimedefretry(times3,delay1):带参数的装饰器允许自定义重试次数和间隔defdecorator(func):# 第二层接收被装饰的函数wraps(func)defwrapper(*args,**kwargs):# 第三层接收函数的实际参数forattemptinrange(times):try:returnfunc(*args,**kwargs)exceptExceptionase:ifattempttimes-1:raise# 最后一次还是失败抛出print(f重试{attempt1}/{times}{e})time.sleep(delay)returnNonereturnwrapperreturndecorator# 第一层返回装饰器# 使用retry(times3,delay0.5)deffetch_data(url):# 模拟不稳定的API调用importrandomifrandom.random()0.7:raiseConnectionError(连接超时)return数据resultfetch_data(https://api.example.com)print(result)理解三层嵌套的关键是把retry(times3, delay0.5)拆开看# retry(times3, delay0.5) 等价于# fetch_data retry(times3, delay0.5)(fetch_data)# └────── 先调用 retry返回 decorator ──────┘# └── 再用 decorator 装饰 fetch_data ──┘第一层retry接收装饰器自己的参数返回第二层decorator第二层接收被装饰的函数返回第三层wrapper第三层才是真正被调用的函数。这就是带参装饰器三层嵌套的由来。收藏提示③无参装饰器两层嵌套带参装饰器三层嵌套。背住这个规律以后手写装饰器不会再懵。核心是记住deco(args)等于func deco(args)(func)。四、数据管道里的4个实用装饰器下面4个装饰器都是我在香港数据管道项目里真实用过的。它们覆盖了数据工程的四个高频需求容错、性能、缓存、校验。装饰器1retry —— 爬虫容错调用政府API时网络抖动、限流、服务器临时故障是常态。手动写try/except重试会让代码又臭又长fromfunctoolsimportwrapsimporttimedefretry(times3,delay1,exceptions(Exception,)):defdecorator(func):wraps(func)defwrapper(*args,**kwargs):forattemptinrange(1,times1):try:returnfunc(*args,**kwargs)exceptexceptionsase:ifattempttimes:print(f[retry]{func.__name__}失败{times}次放弃:{e})raiseprint(f[retry]{func.__name__}第{attempt}次失败({e}){delay}秒后重试)time.sleep(delay)returnwrapperreturndecoratorretry(times3,delay0.5)deffetch_mtr_schedule(line,station):# 调用港铁API不稳定的模拟importrandomifrandom.random()0.5:raiseConnectionError(rt.data.gov.hk 连接超时)returnf{line}{station}到站信息装饰器2timing —— 性能分析数据管道里哪个环节最慢给每个函数加计时一眼定位瓶颈fromfunctoolsimportwrapsimporttimedeftiming(func):wraps(func)defwrapper(*args,**kwargs):starttime.perf_counter()resultfunc(*args,**kwargs)elapsedtime.perf_counter()-startifelapsed1:# 只打印慢于1秒的避免日志刷屏print(f[timing]{func.__name__}耗时{elapsed:.2f}秒 ⚠️)else:print(f[timing]{func.__name__}耗时{elapsed:.3f}秒)returnresultreturnwrappertimingdefprocess_rental_csv():time.sleep(2.5)# 模拟慢操作return处理完成process_rental_csv()# 输出: [timing] process_rental_csv 耗时 2.50秒 ⚠️装饰器3cache —— API缓存同一个API短时间内重复调用纯属浪费。用一个简单的内存缓存装饰器fromfunctoolsimportwrapsimporttimedefcache(ttl30):带TTL的简单内存缓存装饰器_cache{}defdecorator(func):wraps(func)defwrapper(*args,**kwargs):keystr(args)str(sorted(kwargs.items()))ifkeyin_cache:value,timestamp_cache[key]iftime.time()-timestampttl:returnvalue resultfunc(*args,**kwargs)_cache[key](result,time.time())returnresultreturnwrapperreturndecoratorcache(ttl10)defget_air_quality(station):# 模拟昂贵的API调用time.sleep(1)returnf{station}AQHI 数据提示Python 标准库自带了functools.lru_cache如果只需要无过期时间的缓存直接用现成的不必自己写。我这里的cache(ttl30)是为了支持缓存过期这个 lru_cache 不提供的功能。装饰器4validate —— 数据校验复用第1篇的正则这是第1篇正则校验码的延续。把身份证校验逻辑封装成装饰器函数只处理已经校验通过的数据fromfunctoolsimportwrapsdefvalidate_hkid(func):校验参数中的香港身份证号码无效则跳过wraps(func)defwrapper(hkid,*args,**kwargs):# 复用第1篇的校验逻辑importre patternre.compile(r^([A-Z]{1,2})(\d{6})[(]([0-9A])[)]$)mpattern.match(hkid.strip().upper())ifnotm:print(f[validate] 跳过无效格式:{hkid})returnNone# ... 这里省略校验码算法的完整实现见第1篇returnfunc(hkid,*args,**kwargs)returnwrappervalidate_hkiddefprocess_record(hkid):returnf处理记录:{hkid}print(process_record(A123456(3)))# 处理print(process_record(INVALID))# 跳过五、装饰器栈多个装饰器叠加一个函数可以同时套多个装饰器从下往上依次执行timingretry(times3,delay0.5)cache(ttl10)deffetch_data(url):time.sleep(1)return数据执行顺序fetch_data先被cache包裹再被retry包裹最后被timing包裹。调用时的执行流程是timing → retry → cache → fetch_data返回时反向。注意装饰器的顺序如果cache在最内层最靠近函数那么缓存命中时retry和timing都不会执行——这正是我们想要的缓存命中就不需要重试和计时了。六、总结装饰器不是高级技巧而是工程化的基本功。它的本质就是一句话函数是一等公民可以传参数、可以当返回值。四个要点装饰器 高阶函数 闭包deco就是func deco(func)必须加wraps(func)否则函数名和 docstring 会丢无参两层、带参三层背住deco(args)deco(args)(func)装饰器栈从下往上执行内层装饰器如缓存能短路外层装饰器如重试这4个装饰器retry/timing/cache/validate覆盖了数据管道的四个高频需求。把它们套在你自己的函数上你会发现那些重复的try/except、time.time()、缓存字典、校验判断全都消失了。这篇和第1篇正则校验码是一个闭环——第1篇写校验逻辑怎么实现这篇写校验逻辑怎么优雅地集成到代码里。本文为 Python 装饰器技术分享。4个装饰器均为作者在数据管道项目中的实际应用。validate 装饰器复用了本账号0814第1篇文章中的香港身份证校验码算法。

相关新闻

DeepAgents智能体开发框架:从LLM到AI应用落地的实践指南

DeepAgents智能体开发框架:从LLM到AI应用落地的实践指南

1. 从零开始:DeepAgents是什么,以及为什么你需要关注它最近在AI圈子里,DeepAgents这个词的热度突然就上来了。作为一个长期关注AI应用落地的从业者,我习惯性地去扒了一下它的底细。简单来说,DeepAgents不是一个单一的工…

2026/10/12 0:01:55 阅读更多 →
Python正则表达式实战:解析和验证香港身份证、车牌、电话格式

Python正则表达式实战:解析和验证香港身份证、车牌、电话格式

文章目录 环境信息前言:正则能匹配"格式",但匹配不了"真假"一、香港身份证号码的格式:三层结构二、校验码算法:mod 11 加权三、香港车牌格式:传统 vs 自定义四、香港电话号码格式五、完整验证器&a…

2026/10/15 4:16:16 阅读更多 →
Kodi 字幕下载太折腾?这款免费字幕插件 5 分钟解决找字幕难题

Kodi 字幕下载太折腾?这款免费字幕插件 5 分钟解决找字幕难题

Kodi 字幕下载太折腾?这款免费字幕插件 5 分钟解决找字幕难题 【免费下载链接】zimuku_for_kodi Kodi 插件,用于从「字幕库」网站下载字幕 项目地址: https://gitcode.com/gh_mirrors/zi/zimuku_for_kodi 每次打开 Kodi 准备看一部新美剧&#xf…

2026/10/10 11:45:59 阅读更多 →

最新新闻

Tuxedo客户端域连接配置与TPEDOMAIN错误排查指南

Tuxedo客户端域连接配置与TPEDOMAIN错误排查指南

1. 项目概述:这不是一次普通网络连通测试,而是一次面向生产环境的域身份验证闭环验证“tuxedo域连接配置与测试(客户端测试)”这个标题里藏着三个关键信号:tuxedo——不是Linux发行版Tuxedo OS,而是BEA&…

2026/10/15 9:14:01 阅读更多 →
AMD 82亿美元押注世界模型:从芯片到物理AI的生态布局

AMD 82亿美元押注世界模型:从芯片到物理AI的生态布局

1. 一笔天价收购背后的行业信号2024年夏天,一条消息在芯片圈和AI圈同时炸开:AMD宣布以约6.65亿美元现金加股票的方式收购欧洲最大的私人AI实验室Silo AI,整体交易价值约6.65亿美元。而更早之前,AMD还完成了一笔让整个行业侧目的操…

2026/10/15 9:14:01 阅读更多 →
SpringBoot+Vue企业级电商系统架构设计与实战

SpringBoot+Vue企业级电商系统架构设计与实战

1. 项目概述:企业级电商系统的技术选型与价值最近在整理一个基于SpringBootVue的企业级服装商城管理系统,这套方案在中小型电商项目中非常典型。不同于简单的Demo项目,这套架构真正考虑了高并发场景下的稳定性、前后端协作效率以及可维护性。…

2026/10/15 9:14:01 阅读更多 →
AI模型训练暂停背后:智能体安全边界与DNS防护实战

AI模型训练暂停背后:智能体安全边界与DNS防护实战

1. 从一条新闻说起:模型训练为什么会被按下暂停键前几天有个消息在圈子里传得挺开:某头部AI机构宣布暂停其最强模型的训练,原因不是算力不够,也不是资金断裂,而是训练过程中出现了一系列失控事件——用户照片被外泄、智…

2026/10/15 9:14:01 阅读更多 →
Django足球训练营管理系统开发实践

Django足球训练营管理系统开发实践

1. 项目概述这个基于Django的足球训练营管理系统,是我为一家职业足球俱乐部开发的综合性解决方案。系统主要解决三个核心问题:俱乐部日常管理、球员训练计划制定和球员能力评估。在实际使用中,这套系统已经帮助俱乐部将球员评估效率提升了60%…

2026/10/15 9:14:01 阅读更多 →
Mac本地生图实战:Stable Diffusion与MPS加速,182秒一张、10GB内存

Mac本地生图实战:Stable Diffusion与MPS加速,182秒一张、10GB内存

1. 为什么要在 Mac 上折腾本地生图先把结论摆在最前面:在 Mac 上跑本地生图,目前最现实的路径就是基于 Stable Diffusion 系列模型配合 Apple Silicon 的 MPS 加速,用 10GB 左右统一内存换一张 512512 到 768768 的图,单张耗时大概…

2026/10/15 9:12:55 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →