Python技术迷

Python 的闭包是如何实现的?nonlocal 关键字的作用是什么?

学 Python 的时候,很多人第一次遇到“闭包”这个词,都觉得挺玄乎的,好像是某种很高级的技巧。其实闭包本质上不复杂,它就是一个函数把外层作用域里的变量“记住”了。用起来挺顺手,但理解原理和 nonlocal 的作用,能让你少踩不少坑。

闭包是怎么回事

先随便写个例子:

defmake_counter():
    count = 0
defcounter():
nonlocal count
        count += 1
return count
return counter

c1 = 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 全部免费领