Python 的闭包是如何实现的?nonlocal 关键字的作用是什么?
学 Python 的时候,很多人第一次遇到“闭包”这个词,都觉得挺玄乎的,好像是某种很高级的技巧。其实闭包本质上不复杂,它就是一个函数把外层作用域里的变量“记住”了。用起来挺顺手,但理解原理和 nonlocal 的作用,能让你少踩不少坑。
闭包是怎么回事
先随便写个例子:
defmake_counter():
count = 0
defcounter():
nonlocal count
count += 1
return count
return counterc1 = make_counter()
print(c1()) # 1
print(c1()) # 2
这里 counter 这个函数就是闭包,它记住了 make_counter 里的 count 变量。哪怕 make_counter 已经返回结束,counter 还能继续用这个 count。
为什么?因为 Python 在函数定义的时候,不光存了函数的代码,还把用到的外层变量打包在 __closure__ 里保存下来。你要是不信,可以直接看:
print(c1.__closure__)
print([cell.cell_contents for cell in c1.__closure__])
输出里你能看到,count 就活在那里面。这个就是闭包的底层实现方式。
如果不用 nonlocal 会怎样
上面那段代码里用了 nonlocal count,如果你去掉,会报错:
defmake_counter():
count = 0
defcounter():
count += 1# UnboundLocalError
return count
return counter
报错的原因是,Python 解释器在编译 counter 的时候发现你写了 count += 1,它会觉得你要在 counter 里面定义一个新的局部变量 count。结果原来的 count 就被遮蔽了,但这个新变量又没初始化,所以直接炸了。
nonlocal 的作用就是告诉解释器:别给我创建新的局部变量,我就是要用外层函数作用域里的那个变量。
这在写闭包的时候特别常见。比如计数器、缓存函数、装饰器这些地方,很多逻辑都是通过 nonlocal 把外层状态保存住。
再举个装饰器的例子:
defcall_times(func):
count = 0
defwrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"{func.__name__} called {count} times")
return func(*args, **kwargs)
return wrapper@call_times
defgreet(name):
print("Hello,", name)
greet("Tom")
greet("Jerry")
运行结果大概是:
greet called 1 times
Hello, Tom
greet called 2 times
Hello, Jerry
这就是 nonlocal 在闭包里最常用的场景。
如果外层变量再往外一层,比如全局变量,那就不用 nonlocal,而是 global。区别在于:
global改的是模块级变量nonlocal改的是外层函数里的变量
记住这点就够用了。
总结一下:闭包就是函数加上它周围的环境变量打包在一起,形成了一个“带记忆的函数”。而 nonlocal 是闭包里操作外层局部变量时必不可少的关键字,不然就会遇到作用域的坑。
你要是经常写装饰器或者一些带状态的函数,闭包 + nonlocal 绝对是老朋友。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领