global 与 nonlocal
当函数需要修改外层作用域的变量时,单纯的赋值操作会创建局部变量,导致 UnboundLocalError。global 和 nonlocal 两个关键字就是用来打破这一限制的:它们告诉 Python,某个名称不是局部变量,而是外层命名空间中的已有变量。
global 语法与作用
global 声明把变量标记为全局变量,函数内的赋值会直接修改模块级别的全局命名空间:
counter = 0
def increment():
global counter
counter += 1
return counter
print(increment()) # 1
print(increment()) # 2
print(counter) # 2
没有 global 声明时,counter += 1 会被编译为"读取局部变量 counter 并加 1 后写回",但局部命名空间中还没有 counter,所以抛出 UnboundLocalError。
global 可以在函数内任何位置声明,但习惯上放在函数开头。声明后,该名称在函数内任何地方都指向全局变量:
x = "global"
def demo():
print(x) # 这里还没声明 global,但只是读取,所以去全局找
global x # 声明后,x 指向全局
x = "modified"
demo()
print(x) # modified
注意:global 声明必须在赋值之前,但可以在读取之后。不过为了可读性,建议把 global 放在函数体最前面。
global 的使用场景
global 适合以下场景:
- 模块级别的状态计数器:如上述
counter示例。 - 配置标志:模块级别的开关变量,多个函数共同修改。
- 缓存:函数间共享的缓存字典。
_cache = {}
def get_data(key):
global _cache
if key not in _cache:
_cache[key] = expensive_computation(key)
return _cache[key]
但过度使用 global 会导致代码难以追踪和测试。更好的做法是把状态封装到类中,或使用闭包。global 应作为最后手段,而非首选方案。
nonlocal 语法与作用
nonlocal 声明把变量标记为外层(非全局)变量,用于嵌套函数中修改外层函数的局部变量:
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
nonlocal 与 global 的关键区别:
global指向模块全局命名空间nonlocal指向最近的外层函数命名空间(不包括全局)
如果外层没有该名称,nonlocal 会报错:
def outer():
def inner():
nonlocal x # SyntaxError: no binding for nonlocal 'x' found
x = 1
inner()
outer()
nonlocal 与多层嵌套
nonlocal 会逐层向外查找,直到找到第一个匹配的名称:
def level1():
x = "level1"
def level2():
x = "level2"
def level3():
nonlocal x
x = "level3" # 修改的是 level2 的 x,不是 level1 的
level3()
print(x) # level3
level2()
print(x) # level1
level1()
如果需要在最内层修改最外层的变量,而中间层也有同名变量,Python 没有直接机制跳过中间层。这种情况下应考虑重构,避免同名变量造成混淆。
常见错误
用 global 代替 nonlocal:
def outer():
x = "outer"
def inner():
global x # 错误!这会创建/修改全局变量 x,不是 outer 的 x
x = "global"
inner()
print(x) # outer(outer 的 x 未被修改)
outer()
print(x) # global(全局命名空间被污染)
忘记声明就修改外层变量:
def outer():
count = 0
def inner():
count += 1 # UnboundLocalError
return count
return inner
试图用 nonlocal 修改全局变量:
x = "global"
def demo():
def inner():
nonlocal x # SyntaxError: no binding for nonlocal 'x' found
x = "inner"
inner()
demo()
nonlocal 不能用于全局变量,全局变量必须用 global。
与闭包的关系
nonlocal 是闭包修改状态的标准方式。没有 nonlocal,闭包只能读取外层变量,不能修改:
def make_accumulator():
total = 0
def add(n):
# 没有 nonlocal,只能读取 total
return total + n # 可以读取,但无法累积
return add
acc = make_accumulator()
print(acc(10)) # 10
print(acc(5)) # 5,不是 15!因为没有修改 total
加上 nonlocal 后,闭包才能真正"记住"并更新状态:
def make_accumulator():
total = 0
def add(n):
nonlocal total
total += n
return total
return add
acc = make_accumulator()
print(acc(10)) # 10
print(acc(5)) # 15
可变对象的替代方案
如果外层变量是可变对象(列表、字典),可以通过原地修改避免使用 nonlocal:
def make_counter():
count = [0] # 用列表包装
def counter():
count[0] += 1 # 原地修改,不是重新绑定
return count[0]
return counter
这种技巧在某些场景下可以简化代码,但语义不如 nonlocal 清晰。推荐优先使用 nonlocal,让意图更明确。
小结
global 声明变量为全局,允许函数修改模块级变量;nonlocal 声明变量为外层局部,允许嵌套函数修改 enclosing 作用域的变量。两者都解决"赋值创建局部变量"的问题,但指向不同的命名空间。合理使用它们可以实现状态持久化,但过度依赖会损害代码的模块化。优先考虑参数传递、返回值和类封装,必要时再用 global 和 nonlocal。