Python 中的闭包

张开发
2026/4/8 18:40:39 15 分钟阅读

分享文章

Python 中的闭包
Python闭包核心概念与实战应用闭包在Python中是一个挺有意思的概念。简单来说就是在一个函数内部再定义一个函数这个内部函数还能记住外部函数的变量。即使外部函数执行完了内部函数依然可以访问那些变量。闭包在函数式编程里用得比较多。在Python中它有个很重要的用途——写装饰器。搞懂闭包对理解装饰器会有很大帮助。本文主要讲这几个内容闭包是什么Python底层是怎么实现的实际开发中哪些场景会用到闭包不用闭包的话还有什么替代方案一、闭包到底是什么先看一段代码defouter():...msgHello...definner():...print(msg)...returninner...funcouter()func()Helloouter()函数里面定义了一个inner()函数最后把inner返回了。这时候func拿到的就是一个闭包对象。有意思的是outer()执行完之后按理说msg这个局部变量应该被销毁了但调用func()时还能正确打印出msg的值。这就是闭包的特性——它记住了外部函数的变量。要形成一个闭包需要满足三个条件有一个外部函数比如outer外部函数里定义了变量比如msg外部函数里定义了一个内部函数并且这个内部函数引用了外部函数的变量外部函数把内部函数返回了关于变量捕获闭包捕获的变量可以是任何类型。来看个例子defcreate_multiplier(factor):...defmultiplier(x):...returnx*factor...returnmultiplier...doublecreate_multiplier(2)triplecreate_multiplier(3)double(5)10triple(5)15这里factor被捕获到了闭包里面。double和triple是两个不同的闭包实例各自记住了自己的factor值。修改捕获的变量如果闭包要修改捕获的变量需要根据变量类型区分处理情况一变量是不可变类型数字、字符串、元组等需要用nonlocal关键字声明defcreate_counter():...count0...defcounter():...nonlocalcount...count1...returncount...returncounter...c1create_counter()c1()1c1()2c2create_counter()c2()1nonlocal告诉Python这个count不是当前函数的局部变量要去上一层作用域找。每个闭包实例都有自己的count副本互不影响。情况二变量是可变类型列表、字典等可以直接修改不需要nonlocaldefcreate_collector():...items[]...defcollector(item):...items.append(item)...returnitems...returncollector...collectcreate_collector()collect(a)[a]collect(b)[a,b]items是列表直接调用append修改内容就行。二、闭包能干哪些事1. 做个函数工厂有时候我们需要批量生产功能相似但参数不同的函数闭包很适合干这个活。举个例子写个计算不同幂次的函数defmake_power(exponent):...defpower(base):...returnbase**exponent...returnpower...squaremake_power(2)cubemake_power(3)square(5)25cube(5)125make_power就是个工厂传入指数返回对应的幂函数。2. 做个有状态的函数普通函数每次调用完状态就丢了闭包可以把状态保留下来。比如实现一个累计求和的函数defcreate_accumulator():...total0...defaccumulator(value):...nonlocaltotal...totalvalue...returntotal...returnaccumulator...acccreate_accumulator()acc(10)10acc(20)30acc(5)35每次调用acctotal都会累加这个状态就保存在闭包里面。3. 做回调函数在GUI编程或者异步编程中经常需要给按钮绑定一个回调函数这个回调函数可能需要携带一些额外的数据。闭包正好可以解决这个问题。下面是个简单的tkinter例子importtkinterastk wintk.Tk()win.title(Demo)win.geometry(300x200)labeltk.Label(win,text,font(Arial,14))label.pack(pady20)defmake_greeting(text):defgreet():label.config(texttext)returngreet btntk.Button(win,textClick Me,commandmake_greeting(Hello, World!))btn.pack()win.mainloop()make_greeting接收要显示的文字返回一个无参数的闭包正好满足command的要求。三、闭包和装饰器装饰器本质上就是闭包的一种应用。它接收一个函数返回一个增强过的函数。看个例子写个计时装饰器importtimedeftimer(func):defwrapper(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)endtime.time()print(f{func.__name__}took{end-start:.4f}seconds)returnresultreturnwrappertimerdefslow_function(n):time.sleep(n)returnn slow_function(2)输出类似slow_function took 2.0023 secondstimer就是个闭包wrapper捕获了func这个变量在执行前后加了计时逻辑。四、用闭包做缓存缓存是提升性能的常用手段。闭包可以用来实现一个简单的缓存装饰器defcache(func):store{}defwrapper(n):ifnnotinstore:print(fcomputing{n}...)store[n]func(n)else:print(fusing cached result for{n})returnstore[n]returnwrappercachedeffibonacci(n):ifn1:returnnreturnfibonacci(n-1)fibonacci(n-2)print(fibonacci(10))print(fibonacci(10))运行结果computing 10... 55 using cached result for 10 55第一次计算fibonacci(10)会递归计算所有值并缓存第二次直接从缓存取效率大大提升。五、用闭包做数据封装Python的类没有真正的私有变量用闭包可以模拟出私有数据的效果defcreate_stack():data[]# 这个变量外部访问不到defpush(item):data.append(item)defpop():ifdata:returndata.pop()raiseIndexError(pop from empty stack)defsize():returnlen(data)# 返回一个包含方法的字典也可以返回一个函数对象并绑定方法return{push:push,pop:pop,size:size}stackcreate_stack()stack[push](1)stack[push](2)print(stack[size]())# 2print(stack[pop]())# 2print(stack[size]())# 1# stack[data] # 报错访问不到data变量被闭包捕获了外部代码无法直接访问和修改只能通过提供的push、pop等方法操作。这在一定程度上实现了数据封装。六、不想用闭包的话还有什么选择闭包写起来简洁但有时候可读性不够好尤其是逻辑复杂的时候。用类可以替代闭包代码结构更清晰。把上面的计数器用类重写一下classCounter:def__init__(self):self._count0def__call__(self):self._count1returnself._countdefreset(self):self._count0cCounter()print(c())# 1print(c())# 2c.reset()print(c())# 1实现了__call__方法后类的实例就可以像函数一样被调用。相比闭包类的优点是可以有多个方法比如reset属性也更容易查看和调试。七、总结闭包的核心就是函数可以记住定义时的环境变量。这个特性在很多场景下都很有用工厂模式批量生成配置不同的函数状态保持跨多次调用保存数据装饰器在不修改原函数的情况下增强功能缓存避免重复计算数据封装模拟私有变量闭包写起来简洁但如果逻辑比较复杂用类来实现可能是更好的选择。理解闭包对写出更优雅的Python代码会有帮助。回顾一下闭包是什么怎么创建一个闭包闭包捕获变量的机制和修改方法闭包的常见应用场景用类替代闭包的做法

更多文章