Python 中 global 和 nonlocal 的区别
作用域基础:先理解变量的地盘
在深入 global 和 nonlocal 之前,必须建立作用域的概念。Python 采用 LEGB 规则查找变量:
- L (Local) – 当前函数内部
- E (Enclosing) – 外层嵌套函数的局部作用域
- G (Global) – 模块(文件)全局作用域
- B (Built-in) – 内置作用域(如
print、len)
当你在一个函数内给变量赋值时,Python 默认会创建一个新的局部变量,即使外层存在同名变量。global 和 nonlocal 就是用来自定义打破这个默认行为的声明。
global 关键字:直通全局作用域
作用
在函数或类内部声明一个变量为全局变量,后续对该变量的赋值与读取都直接操作全局作用域中的那个名字。
核心规则
- 只有在需要修改全局变量的绑定(重新赋值)时才必须使用
global。 - 如果只是读取一个全局变量的值(如访问列表的元素、调用对象方法),无需
global。 global可以一次声明多个变量,用逗号隔开:global x, y, z。
示例与解释
count = 0 # 全局变量
def increment():
global count # 声明下面使用的 count 是全局的那个
count += 1 # 现在修改的是全局 count
def just_read():
print(count) # 只是读取,无需 global
increment()
print(count) # 输出 1
如果去掉 global count,count += 1 会被视为局部变量未赋值就引用的错误(UnboundLocalError)。
典型使用场景
- 函数需要维护跨多次调用的状态计数器。
- 需要修改模块级别的配置标志(如
debug = True)。 - 注意:滥用全局变量会让代码难以维护。优先考虑传参、返回值或类封装。
nonlocal 关键字:影响外层嵌套函数的变量
作用
在嵌套函数内部,声明一个变量来自最近的、非全局的外层作用域(Enclosing 作用域)。它让内部函数可以重新绑定外层函数的局部变量。
核心规则
nonlocal仅适用于嵌套函数结构(一个函数定义在另一个函数内部)。- 它不会去触碰全局作用域。如果外层没有该变量,则报错
SyntaxError。 - 与
global一样,只有需要重新赋值时才需要nonlocal;仅读取外层变量时无需声明。
示例与解释
def outer():
x = 10 # outer 的局部变量(对于 inner 来说是 enclosing 变量)
def inner():
nonlocal x # 声明 x 是外层函数的那个 x
x += 5 # 修改 outer 中的 x
print("inner:", x)
inner()
print("outer:", x)
outer()
# 输出:
# inner: 15
# outer: 15
去掉 nonlocal x 会触发 UnboundLocalError,因为 x += 5 需要先读取 x,此时 Python 会认为 x 是 inner 的局部变量,但尚未赋值。
如果需要修改的是可变对象内部的数据(如往列表里追加元素),则甚至不需要 nonlocal:
def outer():
data = []
def inner():
data.append(1) # 修改的是列表内容,并非对 data 变量重新赋值,合法
inner()
print(data) # 输出 [1]
多重嵌套时的行为
nonlocal 会逐层向外搜索,找到最近的那一层包含该变量的作用域:
def outer():
x = "outer"
def middle():
x = "middle"
def inner():
nonlocal x # 影响 middle 中的 x,而不是 outer 中的
x = "modified"
inner()
print("middle:", x)
middle()
print("outer:", x)
outer()
# 输出:
# middle: modified
# outer: outer
典型使用场景
- 闭包中修改外层自由变量(如计数器、缓存、状态机)。
- 实现函数工厂或装饰器,需要在内部修改外部函数的配置。
global vs nonlocal 快查对照
| 关键词 | 作用范围 | 搜索顺序 | 典型场景 |
|---|---|---|---|
global |
直接绑定到模块全局作用域 | 跳过所有局部作用域 | 修改模块级变量、标志位 |
nonlocal |
绑定到最近的外层非全局作用域 | 从内向外找到第一个匹配 | 闭包内修改外层函数的局部变量 |
记忆口诀
- global – 直达"地球全局"
- nonlocal – 只影响"亲爹"(外层函数),绝不跨越到全局
常见陷阱与最佳实践
-
误以为只要看到外层变量就需要声明
只有重新赋值(=)才需要global/nonlocal。对可变对象的内部修改不需声明。 -
nonlocal用在顶层函数
nonlocal必须在至少两层嵌套结构中使用,否则直接报SyntaxError。 -
同一函数内混合使用
global和nonlocal不能同时作用于同一个变量名,否则逻辑矛盾。 -
循环引入或过度使用
大量依赖全局状态或闭包自由变量会降低代码可读性和可测试性。优先考虑:- 返回值传递状态
- 将状态封装在类实例中
- 对于闭包内的计数器,可使用
itertools.count或可变容器
-
在模块顶层使用
nonlocal
模块顶层没有外层非全局作用域,nonlocal会立即失败。
一图总结:作用域修改选择流程
当你需要在函数内部修改一个外层变量的值时,按以下思路判断:
- 该变量在哪个作用域?
- 全局作用域 → 使用
global - 外层函数作用域(非全局) → 使用
nonlocal - 当前函数作用域 → 正常赋值即可
- 全局作用域 → 使用
- 如果仅仅是读取值或调用对象方法,则不需要任何关键字。
- 如果设计过于依赖外部变量,停下来想想能否用参数传递或类来重构。
掌握这两个关键字,你就能精确控制 Python 的作用域行为,写出更灵活、更正确的闭包与函数。从今往后,UnboundLocalError 不再是迷雾,而是可以轻松解决的明确信号。