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

【Python 装饰器】实战:从计时器到登录验证

从最简单的装饰器开始写,三个例子逐步升级,最后写出 Flask JWT 接口里的login_required。每个例子都能直接运行,建议边看边敲。

引言:回顾通用模板

​ 所有装饰器都是这个结构的变体,先记住它:

defmy_decorator(func):defwrapper(*args,**kwargs):# 前置逻辑(原函数执行前)result=func(*args,**kwargs)# 后置逻辑(原函数执行后)returnresultreturnwrapper

​ 三个位置可以填代码:执行前执行后、以及决定要不要执行原函数。下面三个例子会分别用到这三个位置。

一、例子一:计时器,测量函数执行时间

​ 最实用的入门装饰器。你想知道一个函数跑了多久,但不想在每个函数里都加计时代码。

1.1 先看不用装饰器的写法

importtimedefslow_function():time.sleep(1)print("执行完毕")start=time.time()slow_function()end=time.time()print(f"耗时:{end-start:.2f}秒")

​ 如果有 10 个函数都要计时,每个都写一遍start = time.time()end = time.time(),需要重复写10次。

1.2 用装饰器的写法

importtimedeftimer(func):defwrapper(*args,**kwargs):start=time.time()# 前置:记录开始时间result=func(*args,**kwargs)# 执行原函数end=time.time()# 后置:记录结束时间print(f"{func.__name__}耗时:{end-start:.2f}秒")returnresultreturnwrapper

​ 对照通用模板:前置逻辑是记录开始时间,后置逻辑是记录结束时间并打印差值。func.__name__拿到原函数的名字,这样打印出来就知道是哪个函数。

使用:

@timerdefslow_function():time.sleep(1)print("执行完毕")@timerdeffast_function():print("秒完")slow_function()fast_function()
执行完毕 slow_function 耗时:1.00 秒 秒完 fast_function 耗时:0.00 秒

@timer往上一挂就行,想给哪个函数计时就挂哪个,不需要改函数本身的代码。不想计时了?把@timer删掉就行,函数本身一行都不用动。

二、例子二:日志记录,记录谁调用了什么

​ 升级一下,写一个记录函数调用信息的装饰器:函数名、参数、返回值。

deflog(func):defwrapper(*args,**kwargs):print(f"调用{func.__name__},参数:args={args}, kwargs={kwargs}")result=func(*args,**kwargs)print(f"{func.__name__}返回:{result}")returnresultreturnwrapper

​ 使用:

@logdefadd(a,b):returna+b@logdefgreet(name,greeting="你好"):returnf"{greeting}{name}"add(1,2)greet("zhangsan")greet("lisi",greeting="嗨")
调用 add,参数:args=(1,2),kwargs={}add返回:3 调用 greet,参数:args=('zhangsan',),kwargs={}greet 返回:你好,zhangsan 调用 greet,参数:args=('lisi',),kwargs={'greeting':'嗨'}greet 返回:嗨,lisi

​ 这里能看到*args**kwargs的实际效果:add(1, 2)的两个参数被args捕获为元组(1, 2)greet("lisi", greeting="嗨")的关键字参数被kwargs捕获为字典{'greeting': '嗨'}。不管原函数长什么样,装饰器都能通用。

三、例子三:权限检查,决定"让不让执行"

​ 前两个例子都是"前后加点逻辑,但原函数一定会执行"。这个例子不一样,装饰器要决定原函数能不能执行。这正是login_required的核心模式。

​ 先写一个简单版:检查用户是不是管理员,是才让执行,不是就拒绝。

3.1 权限检查装饰器实现

current_user={"name":"zhangsan","role":"user"}# 模拟当前用户defadmin_required(func):defwrapper(*args,**kwargs):ifcurrent_user["role"]!="admin":print(f"权限不足:{current_user['name']}不是管理员")returnNone# 不执行原函数,直接返回returnfunc(*args,**kwargs)# 是管理员,正常执行returnwrapper

​ 和前两个例子的关键区别:wrapper里有一个if判断,不满足条件就return了,根本不会调用func()原函数被"拦"在了外面。

3.2 删除用户

@admin_requireddefdelete_user(user_id):print(f"已删除用户{user_id}")returnTrue
  • 如果是普通用户,即zhangsan

    delete_user(123)
    # 权限不足:zhangsan 不是管理员
  • 换成管理员:

    current_user={"name":"admin","role":"admin"}delete_user(123)
    已删除用户123

​ 是不是和login_required的模式很像了?login_required做的就是同样的事,检查 Token 是否合法,合法才让请求进入接口函数,不合法就直接返回 401。

四、@wraps:修复装饰器的一个副作用

​ 在进入login_required之前,还需要解决一个问题。

4.1 默认情况

​ 看看装饰后函数的名字:

@timerdefslow_function():"""这是一个慢函数"""time.sleep(1)print(slow_function.__name__)print(slow_function.__doc__)
wrapper None

​ 名字变成了wrapper,文档字符串也丢了。因为slow_function现在指向的是wrapper函数,而不是原来的slow_function

4.2 为什么这不只是"好看"的问题

​ 这在 Flask 里会引发真实的 bug,Flask 用__name__作为路由的 endpoint 名,如果两个视图函数装饰后名字都变成wrapper,Flask 会抛出AssertionError,提示 endpoint 重复注册。

4.3 用 @wraps 修复

​ 从functools导入wraps,加在wrapper上面:

fromfunctoolsimportwrapsdeftimer(func):@wraps(func)# 加这一行defwrapper(*args,**kwargs):start=time.time()result=func(*args,**kwargs)end=time.time()print(f"{func.__name__}耗时:{end-start:.2f}秒")returnresultreturnwrapper@timerdefslow_function():"""这是一个慢函数"""time.sleep(1)print(slow_function.__name__)# slow_function ← 名字保住了print(slow_function.__doc__)# 这是一个慢函数 ← 文档也保住了

@wraps(func)把原函数的名字、文档字符串等属性复制到wrapper上。写装饰器时永远加上@wraps(func),这是最佳实践。

4.4 更新后的通用模板

fromfunctoolsimportwrapsdefmy_decorator(func):@wraps(func)defwrapper(*args,**kwargs):# 前置逻辑result=func(*args,**kwargs)# 后置逻辑returnresultreturnwrapper

五、终极实战:写出 login_required

​ 现在具备了所有知识,可以理解Flask JWT 登录接口里的login_required了。先回顾一下它做的事:

  1. 从请求头取Authorization: Bearer <token>
  2. 取出 Token,用密钥验签
  3. 验签通过 → 把用户信息挂到request.user,放行
  4. 验签失败 → 直接返回 401,不执行接口函数

​ 这就是例子三"权限检查"的模式:判断条件,决定让不让执行。只不过判断条件从"是不是管理员"变成了"Token 是否合法"。

fromfunctoolsimportwrapsfromflaskimportrequest,jsonifyimportjwtdeflogin_required(func):@wraps(func)defwrapper(*args,**kwargs):# 第一步:从请求头取 Tokenauth_header=request.headers.get("Authorization","")ifnotauth_header.startswith("Bearer "):returnjsonify({"error":"缺少 Token"}),401token=auth_header.split(" ")[1]# 第二步:验签try:data=jwt.decode(token,SECRET_KEY,algorithms=["HS256"])exceptjwt.ExpiredSignatureError:returnjsonify({"error":"Token 已过期"}),401exceptjwt.InvalidTokenError:returnjsonify({"error":"无效的 Token"}),401# 第三步:验签通过,放行request.user=datareturnfunc(*args,**kwargs)returnwrapper

​ 对照通用模板看:

模板位置login_required 里做了什么
前置逻辑取 Token、验签
决定是否执行原函数验签失败 → return 401,不调用func()
func(*args, **kwargs)验签通过 → 执行接口函数(如profile()
后置逻辑无(接口返回什么就返回什么)
  • 使用时:

    @app.route("/profile")@login_requireddefprofile():returnjsonify({"name":request.user["username"]})

    @login_required等价于profile = login_required(profile)。请求进来时,先执行wrapper验 Token,通过了才执行原来的profile()

    ​ 和你在例子三里写的admin_required本质上完全一样,只是判断条件更复杂(验签而不是查角色)、返回值更规范(Flask 的 JSON 响应而不是 print)。

六、装饰器的执行顺序

​ Flask 接口上经常挂多个装饰器:

@app.route("/profile")@login_requireddefprofile():...

​ 多个@叠在一起,Python 从最靠近函数的那个开始,一层层往上包:

# Python 实际执行的等价形式:profile=login_required(profile)# 第一步:login_required 先包裹app.route("/profile")(profile)# 第二步:route 再把结果注册到路由表

​ 请求来的时候,调用方向正好反过来:外层先执行,逐层往里走。

app.route负责把这个包好的函数注册进路由表。当请求匹配到/profile时,Flask 调用注册的函数,也就是最外层的login_required的 wrapper(它先验 Token),通过后才调用内层的profile

​ 我们用一个直观的例子来理解可能更为方便:

  • 左边是定义阶段:Python 从最靠近函数的@login_required开始包裹,再往外被@app.route包裹,像穿衣服,贴身的先穿。

  • 右边是调用阶段:与请求进来时方向相反,先经过最外层的路由匹配,再经过login_required验 Token,最后才到达真正的profile() 函数,就像脱衣服,先脱外套。

七、总结

​ 回顾一下这篇做了什么,从同一个模板出发,填不同的逻辑,写出了三种装饰器:

例子在模板里填了什么学到的新东西
计时器func()前后记录时间装饰器的基本写法
日志记录func()前后打印参数和返回值*args, **kwargs让装饰器适配任意函数
权限检查func()前面加if,不满足条件就不调用装饰器可以拦截请求,不是非得调用原函数

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

相关文章:

  • 【限时解密】Python WASM冷启动延迟从1.8s压至83ms的7步法(仅3家头部Web IDE内部流通的调优清单)
  • 立知lychee-rerank-mm效果实测:图文问答匹配,得分一目了然
  • 【最后72小时】Python网关固件升级失败率高达68%?独家披露基于CRC32+双Bank OTA的零宕机回滚机制(含西门子S7-1500兼容补丁)
  • nvim-dap-ui最佳实践:专业开发者的调试工作流终极指南
  • 实测Qwen3-VL-8B:在4090上跑多模态AI,显存占用和速度如何?
  • 5分钟快速上手:用XDMA实现PC到FPGA的高速数据传输(基于PCIe和DMA技术)
  • ARouter依赖注入终极指南:AutowiredServiceImpl如何实现自动化参数注入
  • OpenClaw 2026年华为云1分钟本地云端搭建及使用指南【最全】
  • SQL Server Maintenance Solution企业级部署:大规模环境维护策略
  • Z-Image-Turbo应用实战:电商海报、社交配图快速生成案例
  • tao-8k实战案例分享:如何用LangChain打造技术文档智能助手
  • PyTorch实战(28)——PyTorch深度学习模型部署
  • PicGo翻译质量保障:5步完整审核流程终极指南 [特殊字符]
  • Qwen2.5-32B-Instruct与MySQL集成:智能数据库查询优化方案
  • EMBA高级用法:如何自定义模块和扩展安全分析能力
  • 开源六轴机械臂:千元级工业精度的3D打印创新实践
  • Unity面试题——唐老师模拟面试、每日一题记录
  • GME多模态向量-Qwen2-VL-2B一键部署教程:基于Ubuntu20.04的快速环境搭建
  • Docker Minecraft Server API集成终极指南:第三方服务连接完整方案
  • S2-Pro大模型数据库智能查询实践:自然语言转SQL实战教程
  • 数学符号代码化终极指南:10个核心数学符号的JavaScript实现技巧
  • 【数据结构与算法】第10篇:项目实战:学生信息管理系统(线性表版)
  • Neofetch终极主题切换指南:基于时间与系统状态的智能样式调整
  • DSP2812开发必备:手把手教你从TI官网下载标准头文件和例程(附导入CCS教程)
  • Ollama-for-amd实战指南:AMD GPU本地AI部署从入门到精通
  • FastAPI CORS源验证:打造安全灵活的动态允许列表
  • Crawlee性能监控终极指南:7个关键指标收集与可视化展示技巧
  • OpenClaw智能监控:nanobot镜像实时扫描日志文件发送警报
  • 如何实现FastAPI后端API版本控制:full-stack-fastapi-template的完整演进策略
  • OpenClaw任务稳定性优化:nanobot镜像的3个调参技巧