闭包是什么意思?作用是什么?
闭包这个东西啊,刚学 Python 的时候我也觉得挺玄乎,感觉它神秘兮兮的,像个套娃,里三层外三层,不知道它到底是干啥的。
但真用上了,发现其实它特别实用,尤其是在装饰器、回调函数这些地方,简直是日常开发的神器。
今天就聊聊闭包这玩意儿,咱程序员聊技术,还是得有点代码来镇场子,不过放心,我保证通俗易懂,咱不搞那些花里胡哨的,咱就聊点实在的。
闭包是个什么东东?
我一开始理解闭包,就觉得它像是函数里面套了个函数,结果还能记住外面的变量,离谱不?函数跑完了,按理说外面的变量应该没了,可它偏偏还能记住。这不科学!但其实,这就是闭包的魅力。
简单点说,闭包就是“函数+环境变量”打包在一起的东西。它把函数外面的变量打包起来,给内层函数随时调用,哪怕外层函数已经执行完了,那些变量也不会被销毁,就像给内层函数开了个小灶,啥时候用都能拿到最新的变量值。
拿个栗子来讲
别急,先看一段代码:
defouter():
x = 10
definner():
print(x)
return inner
closure_func = outer()
closure_func() # 输出 10
这就算个简单的闭包。你看,inner 这个函数里用了 x,但 x 是在 outer 里定义的。正常来说,outer 执行完,x 这变量就该没了,可 inner 还记得它,真就离谱!原因是 Python 给 inner 悄悄打包了一份 x,这就是闭包。
闭包和装饰器,天作之合
说实话,闭包要是只用来记个变量,那确实没啥劲,真正好用的是它和装饰器的组合。咱们平时开发,难免要统计个函数执行时间啥的,你不想改原函数代码,那咋办?闭包+装饰器一出,完美解决。
来个小 demo:
import time
defcount_time_wrapper(func):
defimproved_func():
start_time = time.time()
func()
end_time = time.time()
print(f"it takes {end_time - start_time:.4f}s to run {func.__name__}")
return improved_func
@count_time_wrapper
defsay_hello():
time.sleep(1)
print("Hello, world!")
say_hello()
这一套操作下来,say_hello 不仅会执行,还能顺带告诉你跑了多久。这不改动原函数逻辑,又加了功能,稳稳的。其实这里的 count_time_wrapper 就是用了闭包,把 func 这个变量打包给了 improved_func,即便外层函数早就跑完,improved_func 还是能拿到 func。
闭包的那些小细节
不过说实话,闭包这东西,看着简单,里面还是有些小坑的,尤其是变量捕获。举个例子:
defouter():
funcs = []
for i in range(3):
definner():
print(i)
funcs.append(inner)
return funcs
f1, f2, f3 = outer()
f1() # 2
f2() # 2
f3() # 2
你以为这三个函数会输出 0、1、2?不不不,三次全是 2。这就是闭包把变量引用给记住了,等函数执行时,i 已经变成 2 了,闭包里用的是引用,不是值。所以要解决这个问题,得用默认参数:
defouter():
funcs = []
for i in range(3):
definner(i=i):
print(i)
funcs.append(inner)
return funcs
这样就能输出 0、1、2 了。因为默认参数把当前的 i 值给锁死了,不跟着外面的变量走。
面试问闭包,怎么回答?
我一般这么说:
闭包是指一个函数在其外部作用域已经结束后,依然可以访问到该作用域中的变量。这依赖于函数对象持有一个自由变量的绑定环境,即使外层函数执行完,这些变量也不会被销毁。
创建闭包有三个条件:
需要有嵌套函数(函数里套函数)。 内层函数引用了外层函数的变量。 外层函数返回内层函数。
闭包的好处在于它能让函数记住外部环境,可以用来封装变量、做函数增强(比如装饰器)。不过用的时候要注意变量捕获是引用不是值,容易踩坑。
我觉得吧,闭包这个东西,理解了它的运行机制,写起来也就顺手了。平时多用在装饰器、回调、工厂函数这些地方,简直是开发小助手。大家有啥闭包的妙用,评论区来整两个?