在 Python 中,作用域规定名称在哪些代码区域可见。名称解析遵循 LEGB 顺序:局部(Local)、外层嵌套(Enclosing)、模块全局(Global)、内置(Built-in)。在函数内读取全局名称通常可直接进行;若要在函数内改写模块级名称,须先使用 global 声明。嵌套函数要改写外层函数的局部变量时,须使用 nonlocal。本节说明各层作用域及两类声明的适用情形。
全局变量与局部变量
模块顶层赋值形成全局变量。函数内部对名称的赋值默认创建局部变量,函数返回后该局部名不可从外部访问。函数内若仅读取全局名、且未在同一函数内对该名赋值,则允许直接读取:
x = 10
def show():
y = 20
print(x)
show()
# print(y) # NameError:y 只在 show 内部存在
LEGB 查找顺序
解释器解析名称时,由内向外在上述四层中查找,先命中者生效:
读取全局变量
下列 say 未对 name 赋值,故沿 LEGB 找到模块中的全局 name:
name = "Tom"
def say():
print(name)
say() # Tom改写全局变量:global
函数体内若对某名称赋值,编译阶段将该名视为局部。在未声明 global 的情况下先读后写同一局部名,可能引发 UnboundLocalError。要修改的是模块级变量时,须显式声明:
count = 0
def add_one():
# count = count + 1 # UnboundLocalError
pass
def add_one_ok():
global count
count = count + 1
add_one_ok()
add_one_ok()
print(count) # 2改写外层函数变量:nonlocal
嵌套函数修改外层函数的局部变量时使用 nonlocal,而非 global(global 仅指向模块顶层):
def outer():
n = 0
def inner():
nonlocal n
n += 1
return n
return inner
counter = outer()
print(counter()) # 1
print(counter()) # 2内层函数在外层返回后仍引用外层变量,是闭包的基础,将在闭包一节进一步说明。
global 与 nonlocal
| 关键字 | 作用对象 | 典型场景 |
|---|---|---|
global | 模块级全局变量 | 函数内读写模块顶层名称 |
nonlocal | 外层嵌套函数的局部变量 | 内层函数更新外层计数器等 |
减少 global 的写法
全局可变状态会使修改来源难以追踪。更稳妥的做法是将值作为参数传入,并通过返回值接回:
def add_one(n):
return n + 1
count = 0
count = add_one(count)
count = add_one(count)
print(count) # 2函数仅依赖参数、仅通过返回值影响外部,更便于测试与阅读。
注意事项
- 同一函数内对某名称既有读取又有赋值,却未声明
global/nonlocal,易引发UnboundLocalError。 - 使用
count += 1却未声明global时,既无法修改全局变量,也可能报错。 - 嵌套函数内仅赋值且未加
nonlocal时,创建的是内层局部名,外层同名变量不变。
本节说明了 Python 中 LEGB 作用域及 global、nonlocal 的用法。下一节介绍模块:import 方式与 __name__ == '__main__'。