Python 闭包与装饰器

news2026/5/16 7:06:13
在 Python 学习中闭包和装饰器是两个既关联又容易混淆的知识点尤其是结合嵌套函数使用时常常分不清执行逻辑。但其实只要抓住核心原理再结合简单案例拆解就能轻松掌握。一、前置回顾函数与局部变量的生命周期要理解闭包首先要回顾函数的基本特性——尤其是局部变量的生命周期这是闭包“变量持久化”特性的基础一基础函数与局部变量函数内部定义的变量称为局部变量它的作用域仅限于函数内部生命周期从函数执行开始到函数执行结束后终止被Python垃圾回收机制销毁。deffunc():# 局部变量仅在func内部可访问local_var10print(局部变量值,local_var)func()# 执行函数输出局部变量值 10# print(local_var) # 报错NameError函数执行完毕后局部变量已销毁二无闭包时局部变量的生命周期没有闭包的情况下函数执行完毕后其内部的局部变量会立即被回收无法再被外部访问defout_func():local_var10# 外部函数的局部变量definner_func():print(内部函数访问局部变量,local_var)# 仅访问不修改inner_func()# 内部函数在外部函数内执行out_func()# 执行外部函数输出内部函数访问局部变量 10# 外部函数执行完毕后local_var被销毁无法再访问# 此时无法通过外部调用inner_func自然也无法访问local_var核心结论无闭包时局部变量随外部函数执行结束而销毁内部函数无法在外部函数执行后继续访问它。二、闭包详解嵌套函数的“隐藏魔法”在讲装饰器之前必须先吃透闭包——因为装饰器的实现本质上依赖于闭包的特性。一有闭包时局部变量的生命周期核心特性闭包的核心作用就是改变局部变量的生命周期让其在外部函数执行完毕后依然能被内部函数访问。defout_func(num1):definner_func(num2):print(num1num2)# 内部函数引用外部函数的局部变量num1returninner_func# 外部函数返回内部函数引用fout_func(10)# 外部函数执行完毕返回inner_func引用f(2)# 输出12此时num110依然存在f(1)# 输出11局部变量num1被持久化关键区别有闭包时由于内部函数引用了外部函数的局部变量Python会将该变量“打包”绑定在内部函数上即使外部函数执行完毕局部变量也不会被销毁实现“变量持久化”。二闭包的核心定义闭包不是一个语法而是一种“现象”必须同时满足以下3个条件有嵌套、有引用、有返回才能称之为闭包函数嵌套外部函数out_func内部定义了一个内部函数inner_func引用外部变量内部函数inner_func引用了外部函数out_func的局部变量num1这个变量不是全局变量也不是内部函数自身的局部变量返回内部函数外部函数的返回值是内部函数的“引用”注意是函数名不加括号不是执行结果。三nonlocal关键字闭包中修改外部函数变量默认情况下内部函数只能访问外部函数的局部变量不能修改它修改会被视为定义新的局部变量。如果需要在内部函数中修改外部函数的局部变量就需要用nonlocal关键字defout_func():count0# 外部函数的局部变量definner_func():nonlocalcount# 声明要修改外部函数的局部变量countcount1# 修改外部函数的变量print(当前计数,count)returninner_func fout_func()f()# 输出当前计数 1f()# 输出当前计数 2f()# 输出当前计数 3补充说明nonlocal仅用于嵌套函数中作用是“声明当前变量是外层函数的局部变量”只能修改外层函数的局部变量不能修改全局变量修改全局变量用global。四闭包的小误区很多新手会有两个常见误解误区1闭包是“外部函数返回内部函数的执行结果”——错返回的是内部函数的引用函数名如果加了括号return inner_func()就是执行内部函数并返回结果不是闭包误区2闭包的变量会被覆盖——如果多次调用外部函数会生成不同的闭包实例各自的变量互不影响比如f1out_func(10)f2out_func(20)f1(1)输出11f2(1)输出21误区3内部函数可以直接修改外部函数变量——错必须用nonlocal声明否则会报错或定义新的局部变量。三、装饰器详解基于闭包的“代码增强神器”理解了闭包装饰器就简单了——装饰器本质上就是一个“接收函数作为参数、返回函数的闭包”它的核心作用是在不修改原函数代码、不改变原函数调用方式的前提下给原函数增加额外功能。结合你写的所有装饰器相关代码从基础到进阶逐步拆解。一无装饰器给函数增加功能的繁琐方式在没有装饰器的情况下要给函数增加额外功能只能手动调用函数时添加代码冗余且可复用性差# 原函数评论功能defcontent():print(评论功能...)# 要给content增加“登录验证”功能只能手动添加print(登录功能...)# 额外功能content()# 原函数调用# 每次调用content都要重复写“登录功能”代码冗余无法复用二有装饰器简洁实现函数增强装饰器的出现就是为了解决上述问题通过闭包封装额外功能实现代码复用# 装饰器本质是闭包封装“登录验证”功能defout_func_login(fn):definner():print(登录功能...)# 额外功能fn()# 调用原函数returninner# 原函数defcontent():print(评论功能...)# 给原函数添加装饰功能不修改原函数代码contentout_func_login(content)content()# 调用方式不变输出登录功能... 评论功能...核心优势无需修改原函数代码无需改变调用方式可重复给多个函数添加相同功能比如给点赞、收藏函数也添加登录验证。三实用装饰器计算程序运行时间装饰器在实际开发中非常实用其中“计算函数运行时间”是高频场景importtime# 时间装饰器计算函数执行耗时deftime_decorator(fn):definner():start_timetime.time()# 记录开始时间fn()# 执行原函数end_timetime.time()# 记录结束时间print(f函数执行耗时{end_time-start_time:.4f}秒)# 计算耗时returninner# 测试函数模拟耗时操作time_decorator# 用语法糖添加装饰器deftest_func():time.sleep(1)# 模拟1秒耗时print(测试函数执行完毕)test_func()# 调用函数输出测试函数执行完毕 函数执行耗时1.00xx秒补充这个装饰器可直接复用给任何需要计算耗时的函数添加装饰无需重复编写计时代码。四进阶装饰器1可变参数的装饰器上面的装饰器只能适配无参数的原函数如果原函数有参数比如带参数的评论、登录函数就需要用可变参数装饰器deflogin_decorator(fn):# 用*args接收位置参数**kwargs接收关键字参数适配所有参数的原函数definner(*args,**kwargs):print(登录验证通过...)fn(*args,**kwargs)# 传递参数给原函数returninner# 原函数1无参数login_decoratordefcontent():print(评论功能...)# 原函数2有位置参数login_decoratordefreply(username,content):print(f{username}回复{content})# 原函数3有关键字参数login_decoratordeflike(username,post_id1001):print(f{username}点赞了帖子{post_id})# 调用测试均能正常执行content()reply(小明,写得真好)like(小红,post_id1002)核心要点*args和**kwargs可以接收任意数量、任意类型的参数让装饰器适配所有类型的原函数提升通用性。五进阶装饰器2同一个函数添加多个额外功能一个函数可以同时添加多个装饰器实现多种额外功能# 装饰器1登录验证defout_func_login(fn):definner(*args,**kwargs):print(登录功能...)fn(*args,**kwargs)returninner# 装饰器2验证功能defout_func_code(fn):definner(*args,**kwargs):print(验证功能...)fn(*args,**kwargs)returninner# 同一个函数添加两个装饰器语法糖写法out_func_login# 后装饰包在最外层先执行out_func_code# 先装饰包在内部后执行defcontent():print(评论功能...)content()# 输出顺序登录功能... → 验证功能... → 评论功能...关键规律装饰器是“层层包裹”的后写的装饰器语法糖包在最外层先执行先写的装饰器包在内部后执行。如果想调整执行顺序交换两个装饰器的位置即可。六进阶装饰器3带参数的装饰器有时候我们需要给装饰器本身传递参数比如控制装饰器的功能开关、指定参数值这就需要带参数的装饰器带参数的装饰器本质是“闭包的嵌套”外层函数接收装饰器参数中层函数接收原函数内层函数实现增强功能。# 带参数的装饰器接收参数控制是否开启日志deflog_decorator(enable_log):# 外层函数接收装饰器参数defdecorator(fn):# 中层函数接收原函数闭包核心definner(*args,**kwargs):ifenable_log:# 根据装饰器参数控制功能print(f日志执行函数{fn.__name__})fn(*args,**kwargs)returninnerreturndecorator# 语法糖写法给装饰器传递参数enable_logTrue/Falselog_decorator(enable_logTrue)# 开启日志defcontent():print(评论功能...)log_decorator(enable_logFalse)# 关闭日志defreply():print(回复功能...)content()# 输出日志执行函数content → 评论功能...reply()# 输出回复功能...无日志补充说明带参数的装饰器语法糖写法是装饰器名(参数)原始写法是content log_decorator(enable_logTrue)(content)两者效果完全一致。七远程使用装饰器核心是“装饰器的复用”——将装饰器定义在一个文件中在其他文件中导入使用提升代码的可维护性# 第一步在decorators.py文件中定义装饰器远程装饰器文件deflogin_decorator(fn):definner(*args,**kwargs):print(远程登录验证...)fn(*args,**kwargs)returninner# 第二步在其他文件中导入并使用比如main.pyfromdecoratorsimportlogin_decorator# 导入远程装饰器login_decoratordefcontent():print(评论功能...)content()# 输出远程登录验证... → 评论功能...核心价值实际开发中常用的装饰器登录、日志、计时会统一放在一个文件中供整个项目的函数复用减少重复代码。八装饰器的简化写法语法糖上面的案例中既有原始写法content 装饰器(content)也有语法糖写法装饰器两者效果完全一致但语法糖更简洁。总结一下# 原始写法defcontent():print(评论功能...)contentout_func_login(content)# 语法糖写法等价于原始写法out_func_logindefcontent():print(评论功能...)# 带参数的装饰器语法糖等价于原始写法log_decorator(enable_logTrue)defcontent():print(评论功能...)四、闭包与装饰器的关联与区别很多人会混淆闭包和装饰器其实一句话就能分清装饰器是闭包的“特殊应用”但闭包不一定是装饰器。一关联装饰器的底层实现依赖闭包所有装饰器基础装饰器、可变参数装饰器、带参数装饰器都满足“函数嵌套、引用外部变量、返回内部函数”这三个闭包的核心条件两者都利用了“变量持久化”的特性闭包持久化外部函数的变量如count、num1装饰器持久化被装饰的原函数引用如fnnonlocal关键字同样适用于装饰器闭包嵌套场景用于修改外层装饰器函数的局部变量。二区别维度闭包装饰器核心目的持久化外部变量实现数据封装如计数器增强函数功能不修改原函数如登录、计时参数要求外部函数参数可任意无强制要求如num1、enable_log中层函数必须接收一个“函数”作为参数被装饰的原函数fn应用场景数据封装、函数嵌套复用变量如计数器、局部变量持久化权限、日志、缓存、计时等函数增强场景可远程复用五、总结一常见错误总结装饰器嵌套顺序搞反记住“后装饰先执行”如果想让验证功能先执行就把out_func_code写在后面out_func_login写在前面闭包返回内部函数时加了括号return inner_func正确return inner_func()错误会直接执行内部函数并返回结果不是闭包混淆global和nonlocal修改全局变量用global修改闭包外层函数的局部变量用nonlocal不要用错装饰器未适配可变参数如果原函数有参数装饰器的inner函数必须用*args和**kwargs接收否则会报错带参数的装饰器漏写中层函数带参数的装饰器需要三层嵌套外层收装饰器参数、中层收原函数、内层实现功能少一层就会报错。二核心总结基础铺垫函数的局部变量生命周期——无闭包时随函数执行结束销毁有闭包时被持久化闭包核心嵌套函数引用外部变量返回内部函数用nonlocal可修改外层函数变量核心作用是“变量持久化”装饰器基础基于闭包实现核心作用是“不修改原函数增强功能”分为原始写法和语法糖写法装饰器进阶可变参数装饰器适配所有原函数、多装饰器嵌套实现多功能增强、带参数装饰器控制装饰器功能、远程装饰器复用装饰器核心区别装饰器是闭包的特殊应用重点在“增强函数”闭包重点在“持久化变量”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2477736.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…