06-Python装饰器从入门到源码(上)-闭包与自由变量

发布时间:2026/6/14 0:58:22

06-Python装饰器从入门到源码(上)-闭包与自由变量 文章目录Python 装饰器从入门到源码上——闭包、自由变量与你的第一个装饰器导入语1 ~ 函数是一等公民——Python 和 Java 的关键差异1.1 函数可以赋值给变量1.2 函数可以作为参数传递1.3 函数内部可以定义函数2 ~ 闭包——内部函数记住了外部的变量2.1 闭包长什么样2.2 为什么记住了——自由变量2.3 闭包的自由变量存在哪——__closure__3 ~ 闭包的经典陷阱——循环中的自由变量3.1 现象3.2 原因分析3.3 修复——用默认参数固化值4 ~ nonlocal——在闭包里修改自由变量4.1 问题4.2 解决方案5 ~ 手写第一个装饰器——计时器思考 总结结尾Python 装饰器从入门到源码上——闭包、自由变量与你的第一个装饰器文章简介装饰器是 Python 面试和进阶的经典分水岭。上篇聚焦装饰器的前置知识——闭包与自由变量。从函数是一等公民讲起逐步推导函数可以作为参数传递 → 函数内可以定义函数 → 内部函数可以捕获外部函数的局部变量闭包 → 闭包的自由变量到底存在哪里 →nonlocal的作用。最后手写第一个装饰器用计时器案例展示装饰器如何在不修改原函数的前提下增加新功能。每一步都配__code__、__closure__等属性验证让你看清闭包的内部结构。 个人主页源码骑士❄专栏传送门《Android开发基础》《python基础课程》⭐️热衷从源码视角拆解技术底层原理将复杂架构讲得通俗易懂 源码骑士的简介5年Android Framework系统开发经验曾主导多项系统级性能优化专项技术栈覆盖Android系统全链路Binder/Handler/AMS/WMS/启动流程及Java后端全家桶Spring MyBatis Redis Oracle累计产出原创技术文章100篇文章以源码拆解为特色被读者评价为看一篇胜过啃一周文档导入语装饰器——Python 面试的高频考点也是很多人学了又忘、忘了又学的一个东西。说实话我当初学到装饰器的时候看到app.route()和staticmethod就觉得这是某种黑魔法。直到后来把闭包彻底搞懂才发现装饰器根本不是魔法——它就是闭包的一个具体应用而已。上篇的任务是帮你建立理解装饰器所需的前置知识闭包。很多人觉得装饰器难的原因就是跳过了闭包直接背语法——这就跟跳过了乘法直接背解方程一样能解题但不知道每一步什么意思。我们把闭包拆透下篇再上装饰器进阶带参数的装饰器、wraps就顺理成章了。1 ~ 函数是一等公民——Python 和 Java 的关键差异1.1 函数可以赋值给变量defgreet(name):returnfHello,{name}saygreet# 把函数赋值给变量不是调用没有括号print(say(世界))# Hello, 世界print(sayisgreet)# True同一个函数对象1.2 函数可以作为参数传递defapply(func,value):returnfunc(value)defdouble(x):returnx*2print(apply(double,5))# 输出101.3 函数内部可以定义函数defouter():definner():# 在函数内部定义另一个函数print(我是内部函数)returninner# 把内部函数作为返回值返回fouter()# f 现在是一个函数f()# 输出我是内部函数这三步——赋值、传参、内部定义——合起来叫函数是一等公民。Java 里要实现类似效果需要匿名内部类或者 Lambda。Python 在语法层面就支持。装饰器就是在这三个特性之上构建的。2 ~ 闭包——内部函数记住了外部的变量2.1 闭包长什么样defmake_multiplier(n):defmultiplier(x):returnx*n# n 是外层函数 make_multiplier 的参数returnmultiplier times_3make_multiplier(3)times_5make_multiplier(5)print(times_3(10))# 输出30print(times_5(10))# 输出50times_3和times_5是两个独立的乘法器它们各自记住了创建时的n值。这就是闭包。2.2 为什么记住了——自由变量在multiplier函数里n不是multiplier的局部变量也不是全局变量——它来自于外层函数make_multiplier。这种不是自己定义、也不在全局、来自外层作用域的变量叫自由变量free variable。2.3 闭包的自由变量存在哪——__closure__defmake_multiplier(n):defmultiplier(x):returnx*nreturnmultiplier times_3make_multiplier(3)print(times_3.__closure__)# 有一个 cell 对象print(times_3.__closure__[0].cell_contents)# 输出3 ← 自由变量 n 的值__closure__是个元组装着一组cell对象每个cell保存了一个自由变量的引用。cell_contents属性就是自由变量当前的值。闭包之所以能记住外部变量是因为 Python 偷偷把这些变量装进了函数对象的__closure__属性里。即使外部函数已经返回了这些变量被 cell 对象引用着不会回收。3 ~ 闭包的经典陷阱——循环中的自由变量3.1 现象funcs[]foriinrange(3):funcs.append(lambda:i)# 三个 lambda各自应该打印 0, 1, 2forfinfuncs:print(f())# 输出2 2 2 ← 全是 23.2 原因分析三个 lambda 都引用的是同一个自由变量i。循环结束时i的值是 2所以三个 lambda 调用时都输出 2。这不是lambda 没记住而是它们记住的是同一个变量i的引用而不是各自拷贝了一份值。3.3 修复——用默认参数固化值funcs[]foriinrange(3):funcs.append(lambdaxi:x)# 默认参数在定义时就确定了值forfinfuncs:print(f())# 输出0 1 2 ✓默认参数的值在函数定义时就计算好了——相当于一个快照。而自由变量一直跟着引用走——只能看到最新的值。这个区别在面试里考了无数遍。4 ~nonlocal——在闭包里修改自由变量4.1 问题defcounter():count0defincrement():count1# ❌ UnboundLocalErrorreturncountreturnincrement为什么报错原因和前面讲的一样——count 1等价于count count 1在 Python 看来这是赋值操作Python 就把count标记为increment的局部变量。但count 1时局部变量count还没赋过值于是 UnboundLocalError。4.2 解决方案defcounter():count0defincrement():nonlocalcount# 声明count 不是我的局部变量用外层那个count1returncountreturnincrement ccounter()print(c())# 1print(c())# 2 ← 每次调用都累加nonlocal告诉 Python这个变量别当局部变量处理顺着作用域链往外找。找到后可以读写它。5 ~ 手写第一个装饰器——计时器把前面学到的串起来写一个计时装饰器importtimedeftimer(func):# ① 接收一个函数defwrapper(*args,**kwargs):# ② 把原函数包一层starttime.perf_counter()resultfunc(*args,**kwargs)# ③ 调用原函数endtime.perf_counter()print(f{func.__name__}耗时{end-start:.6f}秒)returnresult# ④ 返回原函数的返回值returnwrapper# ⑤ 返回包装后的函数timer# ← 装饰器语法defslow_function():total0foriinrange(10_000_000):totalireturntotal resultslow_function()# 输出slow_function 耗时0.382041秒timer等价于slow_functiontimer(slow_function)从内到外看timer(slow_function)返回wrapperwrapper包装了原始函数加了计时逻辑。从此每次调用slow_function()实际执行的是wrapper()。思考 总结上篇的核心——两条地基闭包 内部函数 自由变量存储在__closure__。闭包让内部函数记住创建时的外部变量值而不是每次现场计算。__closure__属性中的cell对象就是闭包的数据储存单元。装饰器 闭包 函数是一等公民。decorator是把目标函数作为参数传给装饰器函数装饰器返回一个包装后的新函数。装饰器的本质就是在不修改原函数的前提下给函数添加新功能。下篇深入装饰器进阶——带参数的装饰器是什么原理、functools.wraps为什么不能省、以及装饰器在实际项目Django / Flask / 权限校验中的真实写法。结尾各位小伙伴上篇到此结束感谢阅读源码骑士 — Python 全栈 系统架构关注跟博主一起从源码视角深耕底层原理见证每一次成长❤️点赞让优质内容被更多人看见让知识传递更有力量⭐收藏把核心知识点存好在需要时随时查、随时用评论分享你的经验或疑问评论区一起交流避坑一键四连不要忘记给博主一键四连哦今日源码拆解达成️寄语技术之路同行的人会让前路更有方向结语闭包是理解装饰器的大门这扇门现在开了。下篇见一键四连别忘了

相关新闻