
Python函数嵌套函数与闭包closure原理详解一、开篇函数里的函数在Python中函数是一等公民——你可以把函数赋值给变量、作为参数传递、甚至在函数内部定义另一个函数。这最后一种用法就是嵌套函数Nested Function而它引出了Python中最优雅的特性之一闭包Closure。⌨️ 先感受一下嵌套函数的基本形式defouter():外层函数messageHello from outer!definner():内层函数——定义在外层函数内部print(message)# 内层函数访问了外层的变量inner()# 外层函数调用内层函数outer()# 输出Hello from outer! 内层函数inner能够访问外层函数outer的变量message——这就是嵌套函数最基本的特性。而如果我们把内层函数返回出去让它在外部被调用它仍然记得外层函数的变量——这就是闭包。这篇文章我们从嵌套函数出发逐步深入到闭包的原理、应用和陷阱。二、嵌套函数基础2.1 为什么要在函数里定义函数# 原因一封装——将辅助函数隐藏在内部defprocess_data(data):处理数据——外部只需要知道这个函数defvalidate(item):验证单个数据项——外部不需要知道这个函数returnitemisnotNoneanditem0deftransform(item):转换单个数据项returnitem*21# 使用内部定义的辅助函数valid_data[itemforitemindataifvalidate(item)]result[transform(item)foriteminvalid_data]returnresultprint(process_data([1,-2,3,None,0,5]))# [3, 7, 11]# validate和transform对外部不可见——很好的封装# 原因二工厂函数——根据参数生产不同的函数defmake_multiplier(factor):创建一个乘以factor的函数defmultiplier(x):returnx*factorreturnmultiplier# 返回内部函数doublemake_multiplier(2)triplemake_multiplier(3)print(double(10))# 20print(triple(10))# 30print(double(5))# 10# 每个函数记住了自己的factor# 原因三回调/事件处理defcreate_button_handler(button_name):创建按钮的点击处理器defhandler():print(f按钮「{button_name}」被点击了)returnhandler save_handlercreate_button_handler(保存)delete_handlercreate_button_handler(删除)save_handler()# 按钮「保存」被点击了delete_handler()# 按钮「删除」被点击了2.2 嵌套函数的变量访问# ⌨️ 内层函数可以访问外层函数的变量defouter():xouter_xyouter_ydefinner():# 可以读取外层变量print(finner看到: x{x}, y{y})# 但不能直接修改需要用nonlocal# x modified # 这会创建inner的局部变量x不影响外层xinner()print(fouter中: x{x}, y{y})outer()# inner看到: xouter_x, youter_y# outer中: xouter_x, youter_y# 多层嵌套deflevel1():aL1deflevel2():bL2deflevel3():cL3print(flevel3看到: a{a}, b{b}, c{c})# LEGB查找L(c)→E(b)→E(a)→G→Blevel3()level2()level1()# level3看到: aL1, bL2, cL3三、闭包带着记忆的函数3.1 什么是闭包# 闭包 函数 它捕获的自由变量# 三个条件# 1. 有嵌套函数内层函数# 2. 内层函数引用了外层函数的变量自由变量# 3. 外层函数返回了内层函数defmake_greeter(greeting):创建一个打招呼的函数defgreeter(name):# greeting是自由变量——它不属于greeter的参数或局部变量# 但greeter引用了它——这就形成了闭包returnf{greeting}{name}returngreeter# make_greeter执行完毕后它的局部变量greeting本应被销毁# 但因为greeter函数引用了它所以greeting被保留了下来hello_greetermake_greeter(你好)morning_greetermake_greeter(早上好)evening_greetermake_greeter(晚上好)print(hello_greeter(张三))# 你好张三print(morning_greeter(李四))# 早上好李四print(evening_greeter(王五))# 晚上好王五# 每个greeter函数都记住了它诞生时的greeting值3.2 查看闭包捕获的变量# ⌨️ __closure__属性查看闭包捕获了哪些自由变量defmake_counter(start0):创建计数器countstart step1defcounter():# 引用了count读取和修改都需要nonlocalpassdefincrement():nonlocalcount countstepreturncountreturnincrement counter1make_counter(0)counter2make_counter(100)# 查看闭包信息print(fcounter1的闭包:{counter1.__closure__})# counter1的闭包: (cell at ...: int object at ..., cell at ...: int object at ...)# 查看每个cell的内容fori,cellinenumerate(counter1.__closure__):print(f cell[{i}] {cell.cell_contents})# cell[0] 0 (count)# cell[1] 1 (step)# 两个闭包是独立的print(counter1())# 1print(counter1())# 2print(counter2())# 101 —— 独立的闭包有自己的count和step# 再次查看——cell内容变了fori,cellinenumerate(counter1.__closure__):print(f cell[{i}] {cell.cell_contents})# cell[0] 2 (count增加了)# cell[1] 1 (step没变)# __code__.co_freevars 查看自由变量名print(f自由变量:{counter1.__code__.co_freevars})# 自由变量: (count, step)3.3 闭包中的变量是引用# 闭包捕获的是变量的引用不是值的拷贝# 这一点非常重要# ⚠️ 经典陷阱defcreate_multipliers_bad():❌ 有问题的版本multipliers[]foriinrange(1,4):# 每个lambda都引用同一个变量imultipliers.append(lambdax:x*i)returnmultipliers# i最终的值是3所有lambda都用i3formincreate_multipliers_bad():print(m(10),end )# 30 30 30print()# ✅ 修正方法一用默认参数冻结值defcreate_multipliers_fix1():使用默认参数捕获当前值multipliers[]foriinrange(1,4):multipliers.append(lambdax,ni:x*n)returnmultipliersformincreate_multipliers_fix1():print(m(10),end )# 10 20 30print()# ✅ 修正方法二用嵌套函数创建独立作用域defcreate_multipliers_fix2():每个迭代创建独立的作用域defmake_mul(n):returnlambdax:x*n# n是make_mul的参数每次调用独立return[make_mul(i)foriinrange(1,4)]formincreate_multipliers_fix2():print(m(10),end )# 10 20 30print()四、闭包的经典应用场景4.1 计数器 / 累加器defmake_counter(start0,step1):创建一个计数器countstartdefcounter():nonlocalcount currentcount countstepreturncurrentdefreset(new_startNone):nonlocalcount countstartifnew_startisNoneelsenew_start counter.resetresetreturncounter cmake_counter(0,2)print(c())# 0print(c())# 2print(c())# 4c.reset(100)print(c())# 100print(c())# 1024.2 缓存 / 记忆化defmemoize(func):为函数添加缓存——经典闭包应用cache{}# 闭包变量缓存结果defwrapper(arg):ifargnotincache:print(f计算{func.__name__}({arg})...)cache[arg]func(arg)else:print(f从缓存获取{func.__name__}({arg}))returncache[arg]defclear_cache():cache.clear()print(缓存已清空)wrapper.clear_cacheclear_cache wrapper.cache_infolambda:dict(cache)returnwrappermemoizedeffibonacci(n):计算斐波那契数ifn2:returnnreturnfibonacci(n-1)fibonacci(n-2)print(fibonacci(10))# 多次计算但每次参数只算一次print(fibonacci(10))# 从缓存获取print(f缓存内容:{fibonacci.cache_info()})fibonacci.clear_cache()4.3 权限检查器defrequire_role(required_role):创建一个角色权限检查器defdecorator(func):defwrapper(user,*args,**kwargs):# user是一个字典包含role字段ifuser.get(role)!required_role:raisePermissionError(f用户{user.get(name)}没有{required_role}权限)returnfunc(user,*args,**kwargs)returnwrapperreturndecorator# 使用admin_user{name:管理员,role:admin}normal_user{name:普通用户,role:user}require_role(admin)defdelete_all_users(user):returnf{user[name]}执行了删除所有用户操作# print(delete_all_users(admin_user)) # 正常执行# delete_all_users(normal_user) # PermissionError!五、闭包 vs 类# ⌨️ 很多时候闭包和类可以互相替代# 选择标准复杂度# 使用闭包——适合简单场景1-2个状态1-2个方法defmake_averager():求平均值——闭包实现total0count0defadd(value):nonlocaltotal,count totalvalue count1returntotal/countreturnadd avgmake_averager()print(avg(10))# 10.0print(avg(20))# 15.0# 使用类——适合复杂场景多个方法、继承、序列化等classAverager:求平均值——类实现def__init__(self):self.total0self.count0defadd(self,value):self.totalvalue self.count1returnself.total/self.countdefreset(self):self.total0self.count0defget_stats(self):return{total:self.total,count:self.count}# 选型建议# 闭包简单、优雅、不可变对外隐藏状态# 类可扩展、可继承、可序列化、可调试能看到属性六、总结闭包是Python函数式编程的核心概念。它让函数记住诞生时的环境为实现装饰器、工厂函数、惰性求值等模式提供了基础。核心要点嵌套函数是闭包的前提——在函数内部定义函数自由变量是闭包的灵魂——内层函数引用了外层函数的变量返回函数让闭包发挥作用——外层返回内层内层带走外层的变量变量是引用不是拷贝——闭包陷阱的根源简单场景用闭包复杂场景用类——各取所长✅闭包 函数 环境这是理解Python装饰器、生成器、上下文管理器等高级特性的基石。