Nolocal 和 Global 关键字的用途是什么?
说到 nonlocal 和 global 这俩关键字,我就忍不住要吐槽一波,尤其是你如果 Python 写得多了,经常能看到一堆初学者被这俩玩意儿搞得一头雾水,然后 Stack Overflow 上也有一堆误导性的回答,让人越看越迷糊……
所以这篇文章,我打算从一个程序员的视角,把这两个关键字掰扯清楚,不光讲明白它们是干嘛的,更要告诉你它们到底应该怎么用,哪些场景用它们会舒服,哪些场景用它们简直是自己挖坑埋自己 🤯
先说结论:global 是用来声明你要操作的是模块级别的全局变量,而 nonlocal 则是用来操作最近一层封闭函数作用域里的变量(闭包里的那种)。听不懂没关系,我们来一点点说。
讲 global 之前,咱们先看看 Python 的作用域规则,也就是 LEGB:
L:Local(局部作用域)—— 当前函数内部 E:Enclosing(闭包函数的外层函数作用域) G:Global(模块级别作用域) B:Built-in(Python 内置作用域)
所以你写个函数,如果里面用到了某个变量,Python 就会按照这个顺序一级一级往外找。
比如你写了个函数这样:
x = 10
deffoo():
print(x)
这时候 foo() 运行时输出啥?答案是 10,因为它在局部作用域找不到 x,就往外走,在模块级别找到了。
但如果你在 foo() 里试图修改这个变量:
deffoo():
x = x + 1
print(x)
你以为你是在修改全局的 x,但实际上 Python 会报错:UnboundLocalError: local variable 'x' referenced before assignment。因为一旦你在函数里赋值一个变量,Python 默认它是个“局部变量”,所以那个 x + 1 的 x,Python 以为是函数内部的,结果你还没声明就用,直接爆炸💥。
这个时候怎么办?你得告诉 Python:“别把我当傻子,我就是要改外面的 x!”于是你得用 global:
x = 10
deffoo():
global x
x = x + 1
print(x)
foo() # 输出 11
这个时候就正常了,因为你告诉 Python:“我要用模块级别的 x,别给我搞局部那一套”。
⚠️注意:global 只能操作模块级别的变量,换句话说,只有你在文件里直接定义的变量才叫“全局”。
接下来说说 nonlocal。这个关键词比较“冷门”,主要是你得在函数嵌套函数的场景里才能用得上。说白了,它就是用来修改“外层函数里定义的变量”。
比如我们常见的闭包:
defouter():
x = 10
definner():
print(x)
return inner
调用 inner = outer(); inner() 会打印 10。没毛病。
但如果你想在 inner 里修改 x 呢?你试试这么写:
defouter():
x = 10
definner():
x += 1
print(x)
return inner
又爆炸:UnboundLocalError。跟上面说的一样,一旦你在函数里赋值,Python 就默认这是个局部变量,然后你还没定义就用了,Python 就开始闹脾气了😡。
这时候就轮到 nonlocal 出场了:
defouter():
x = 10
definner():
nonlocal x
x += 1
print(x)
return inner
foo = outer()
foo() # 输出 11
foo() # 输出 12
为什么能成功?因为你告诉 Python:“我要用的是上一层函数作用域里的那个 x”,于是 Python 不再报错,还能持续更新闭包变量。
这玩意儿在写装饰器、写状态保持的闭包时简直神器 ✨
讲到这儿,我得吐槽一句,nonlocal 这设计...真的是有点反直觉。很多程序员第一次接触都以为 global 能搞定所有外部变量,其实压根不是一回事儿。
我身边有同事就踩过坑,想在一个闭包里更新外层变量,结果用了个 global,然后发现变量压根没变,调试一下午,一脸懵圈 🤷♂️
还有些项目里为了避开这些作用域的坑,干脆写个 class,把状态放类里,直接用 self.xxx 搞定所有变量,根本不和 nonlocal 打交道。虽然说多写几个字,但逻辑上清晰多了。
说到实际应用场景,我给你举几个有意思的例子。
比如你要实现一个简单的计数器闭包:
defmake_counter():
count = 0
defcounter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c()) # 输出 1
print(c()) # 输出 2
这个时候如果你忘了加 nonlocal,结果就是每次调用都抛错,妥妥的坑。
再比如用 global 管理配置参数(虽然我不推荐):
config = {}
defset_config(key, value):
global config
config[key] = value
这写法在小脚本里无伤大雅,但一旦项目大了,配合线程用 global 就会变成地雷⚠️,一不小心就会出现线程共享变量的问题。
所以你说这俩关键字有没有用?有!非常有!但要不要用?我个人的建议是能不用就不用 😂
你有更好的方式可以写得更清晰,别硬拗:
想持久状态?用类( class)想更新外层作用域变量?用 nonlocal,别想当然写个global真要用全局变量?那你得确保项目复杂度不高,或者别在线程里搞共享变量
写到这我突然想到一个段子,说“写 Python 代码最怕的就是作用域不清楚,一会儿 local,一会儿 global,最后你只想说一句:谁 am I,我在哪,我在修改谁的值?”
兄弟们,看懂了没?关键在于你要搞清楚变量作用的边界在哪。如果你不明白自己在操作哪个变量,写出来的代码就注定是个炸弹💣,你自己写的时候没事儿,几天后上线爆了你都不知道为啥。
至于 nonlocal 和 global,这俩关键词就像工具箱里的两个冷门小工具,不是天天用,但关键时刻能救命。问题是:你得知道它们是干嘛用的,别乱用。更别一看个例子就搬进生产代码里,那是玩火🔥!
你要问我,什么时候该用这两个关键字?我就一句话:你确定你非得修改外层作用域的变量,且其他方法不方便实现,那你再用。
反正我个人是不太爱用,宁愿多写几行类,也不想以后自己 debug 自己的作用域迷宫 😅
你们怎么看?用这俩关键词有过什么血泪史么?欢迎来聊聊~