当前位置: 首页 > news >正文

07-Python装饰器从入门到源码(下)-带参数装饰器与wraps

文章目录

  • Python 装饰器从入门到源码(下)——@wraps、带参数的装饰器
    • 导入语
    • 1 ~> `functools.wraps`——别让你的函数丢了身份
      • 1.1 问题根源
      • 1.2 解决方案:`@wraps`
      • 1.3 `@wraps` 做了什么
    • 2 ~> 带参数的装饰器——为什么你需要三层嵌套
      • 2.1 场景:可配置的重试装饰器
      • 2.2 逐步推导
    • 3 ~> 实战装饰器一:权限校验
    • 4 ~> 实战装饰器二:Django `@login_required` 的简化版
    • 5 ~> 装饰器为什么优先选装饰器而不是直接改函数原因总结
    • 思考 && 总结
    • 结尾

Python 装饰器从入门到源码(下)——@wraps、带参数的装饰器

📖文章简介:上篇讲完了闭包和第一个装饰器,下篇解决两个高频问题:(1) 用了装饰器后函数的__name____doc__为什么会丢,以及functools.wraps怎么修;(2) 带参数的装饰器到底是怎么工作的——为什么需要三层嵌套函数。附带三个实战装饰器:权限校验装饰器、带重试次数的网络请求装饰器、Django 的@login_required简化实现。每个装饰器都有完整的逐步拆解和可执行代码。


🎬 个人主页:源码骑士

专栏传送门:《Android开发基础》《python基础课程》

⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂


🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以源码拆解为特色,被读者评价为"看一篇胜过啃一周文档"


导入语

上篇我们写了第一个装饰器——计时器。看着很完美。但有个问题:

@timerdefadd(a,b):"""计算两个数的和"""returna+bprint(add.__name__)# 输出:wrapper ← ???不是 addprint(add.__doc__)# 输出:None ← 文档没了

用了装饰器之后,函数的元信息全丢了。如果你在用 Flask 写路由@app.route('/')__name__丢了影响还不大;但如果你的日志系统靠__name__区分函数来源、或者你写的装饰器被第三方工具用__doc__生成 API 文档时——这种信息丢失就是隐藏的生产事故。这就是functools.wraps出场的原因。下篇我们解决这个问题,顺便把带参数的装饰器也彻底搞明白。


1 ~>functools.wraps——别让你的函数丢了身份

1.1 问题根源

deftimer(func):defwrapper(*args,**kwargs):result=func(*args,**kwargs)returnresultreturnwrapper

timer(add)返回的是wrapper函数。从此add变量指向的是wrapper。所以add.__name__自然是'wrapper'

1.2 解决方案:@wraps

fromfunctoolsimportwrapsdeftimer(func):@wraps(func)# ← 关键!把 func 的元信息拷贝给 wrapperdefwrapper(*args,**kwargs):result=func(*args,**kwargs)returnresultreturnwrapper@timerdefadd(a,b):"""计算两个数的和"""returna+bprint(add.__name__)# 输出:add ✓print(add.__doc__)# 输出:计算两个数的和 ✓

1.3@wraps做了什么

本质上,@wraps(func)等价于:

wrapper.__name__=func.__name__ wrapper.__doc__=func.__doc__ wrapper.__module__=func.__module__ wrapper.__dict__.update(func.__dict__)wrapper.__wrapped__=func# 记录原始函数引用

文档、函数名、模块名、字典属性……全都从原函数复制过来。__wrapped__属性还保留了对原始函数的引用——某些调试工具靠它找回被装饰前的函数。


2 ~> 带参数的装饰器——为什么你需要三层嵌套

2.1 场景:可配置的重试装饰器

@retry(times=3,delay=1)# 你想传参数:重试3次,每次间隔1秒defcall_api():pass

普通装饰器是timer(func)。带参数的装饰器是retry(times=3)(func)——先调用retry(times=3)拿到一个装饰器,再把func传进去。

2.2 逐步推导

importtimedefretry(times,delay):defdecorator(func):# 最外层返回真正的装饰器@wraps(func)defwrapper(*args,**kwargs):forattemptinrange(times):try:returnfunc(*args,**kwargs)exceptExceptionase:ifattempt==times-1:raise# 最后一次也失败了,抛异常time.sleep(delay)returnwrapperreturndecorator@retry(times=3,delay=0.5)defunstable_network_call():importrandomifrandom.random()<0.7:raiseConnectionError("网络超时")return"成功"

三层嵌套的含义:

第1层 retry(times, delay)→ 接收装饰器参数(配置) 第2层 decorator(func)→ 接收被装饰的函数 第3层 wrapper(*args, **kwargs)→ 接收函数的调用参数

带参数的装饰器本质上是一个"返回装饰器的函数"。@retry(times=3, delay=0.5)等价于:

unstable_network_call=retry(times=3,delay=0.5)(unstable_network_call)

3 ~> 实战装饰器一:权限校验

fromfunctoolsimportwrapsdefrequire_role(role):defdecorator(func):@wraps(func)defwrapper(user,*args,**kwargs):ifuser.get("role")!=role:raisePermissionError(f"需要{role}权限,当前为{user.get('role')}")returnfunc(user,*args,**kwargs)returnwrapperreturndecorator@require_role("admin")defdelete_user(user,user_id):returnf"删除用户{user_id}"admin={"name":"张三","role":"admin"}normal={"name":"李四","role":"user"}print(delete_user(admin,42))# ✓ 删除用户 42# delete_user(normal, 42) # ❌ PermissionError: 需要 admin 权限,当前为 user

你不需要在每个敏感接口里写if user.role != "admin",一行@require_role("admin")搞定。


4 ~> 实战装饰器二:Django@login_required的简化版

Django 的@login_required装饰器就是"带条件判断的闭包",核心逻辑拆出来很简单:

fromfunctoolsimportwrapsdeflogin_required(func):@wraps(func)defwrapper(request,*args,**kwargs):ifnotrequest.get("is_authenticated"):return{"error":"未登录","redirect":"/login/"}returnfunc(request,*args,**kwargs)returnwrapper@login_requireddefprofile(request):return{"name":"张三","age":28}print(profile({"is_authenticated":True}))# {'name': '张三', 'age': 28}print(profile({"is_authenticated":False}))# {'error': '未登录', 'redirect': '/login/'}

5 ~> 装饰器为什么优先选装饰器而不是直接改函数原因总结

  • 复用:一个装饰器可以适用于多个函数
  • 解耦:权限逻辑和业务逻辑各自独立管理
  • 可读:@require_role("admin")比函数内部一堆 if 清晰得多
  • 可组合:可以叠多个装饰器:@login_required @require_role("admin") @log_api_call

思考 && 总结

下篇两个核心知识点:

  1. @wraps(func)不能省。装饰器返回的是wrapper函数,如果不做信息恢复,原函数的__name____doc__全部丢失。这是生产环境日志和文档的暗坑。
  2. 带参数的装饰器 = 三层嵌套函数。最外层接收装饰器参数(配置),中间层接收函数,最内层接收调用参数。理解这个结构之后,@app.route('/')@retry(times=3)的原理就完全通了。

结尾

各位小伙伴,装饰器上下篇到此全部结束。感谢阅读!

源码骑士 — Python 全栈 & 系统架构

👀关注:跟博主一起从源码视角深耕底层原理

❤️点赞:让优质内容被更多人看见

收藏:核心知识点存好,随用随查

💬评论:分享你的经验或疑问,一起交流

🔄一键四连:不要忘记给博主"一键四连"哦!

🗡️寄语:技术之路,同行的人会让前路更有方向

结语:装饰器是 Python 的灵魂特性之一,上篇讲闭包,下篇讲应用——两篇读完,面试不怕。一键四连别忘了!

http://www.jsqmd.com/news/1008717/

相关文章:

  • 2026年四川假发市场深度观察:从定制工艺到服务体系的全面解析 - 优质品牌商家
  • 四川污水处理工程技术解析:成都医院学校酒店污水处理/成都医院污水处理设备/厂家实力与场景适配推荐 - 优质品牌商家
  • 2026年成都婚礼筹备全攻略:信誉与实力兼备的婚庆公司深度解析 - 品牌鉴赏官2026
  • 从论文被拒到秒过:手把手教你用MATLAB搞定SCI期刊要求的图表格式(含字体、线型、符号全设置)
  • Direct HTML
  • 2026年新发布:湖北市场专业的折叠标签品牌综合解析与推荐 - 品牌鉴赏官2026
  • 【技术干货】MiniMax M3开源大模型实战:多模态推理+智能体工作流全解析
  • 双路FOC驱动解决方案:如何用低成本ESP32实现专业级无刷电机控制
  • 认知几何学与Gärdenfors概念空间理论:相同点与本质差异的对比分析报告(世毫九实验室原创研究)
  • Flink窗口实战:用Java和Lambda表达式搞定地铁客流实时统计(附完整代码)
  • 新疆公办二本理工类本科院校综合实力盘点 适配低分考生升学择校参考榜单 - 海棠依旧大
  • 告别静态截图!用Matlab Appdesigner + animatedline函数,让Simulink仿真结果“动”起来
  • 2026年风管PVC膜市场格局观察:从材料选型看供应商综合实力 - 优质品牌商家
  • 2026优质凤凰办理公司注销业务公司排行哪家好 - 品牌排行榜
  • 刚性结理论:从拓扑性质到多项式不变量
  • STM32F103C8T6驱动GT20L16S1Y字库芯片实战:OLED屏显示中文保姆级教程
  • 处理AI模型输出文件?手把手教你用Python把JSONL转成标准JSON(避坑字符编码问题)
  • 08-Python异常处理-你写的try-except可能比不写更危险
  • 2026年宜宾淋浴房批发市场观察:本地厂商与区域供应链的差异化竞争力分析 - 优质品牌商家
  • 3分钟上手MMD Tools:Blender中导入导出MMD模型的完整指南
  • 大件行李跨省怎么寄最划算?大件行李跨省寄快递,怎么省钱又省心? - 快递物流资讯
  • 2026达州旧房换窗厂家评测:适配性与服务实力对比 - 优质品牌商家
  • 09-Python模块导入机制-sys.path与循环导入的死锁式排查
  • 用FreeGLUT和OpenGL画个彩色立方体:从glOrtho投影到矩阵变换的完整流程
  • 告别Xftp!AutoDL+JupyterLab一站式搞定YOLOv5文件上传与训练(附数据集管理技巧)
  • 终极指南:Windows平台最佳漫画阅读器E-Viewer完全体验
  • 告别纸上谈兵:用MATLAB仿真帮你搞定汽车传动系统匹配与优化
  • 2026年四川圆柱钢模板厂家实力解析:产能、交付与工程案例综合观察 - 优质品牌商家
  • 2026年近期诚信的天津物流货代业内推荐:聚焦天津港的可靠伙伴 - 品牌鉴赏官2026
  • 2026新疆公办二本院校怎么选?低分稳妥工科本科院校推荐-新疆工业学院 - 海棠依旧大