Python作用域规定一个名字在哪些代码区域可见。查找顺序是 LEGB:先局部 Local,再外层嵌套 Enclosing,再模块全局 Global,最后才是内置 Built-in。读全局变量通常可以直接读,但要在函数里改写全局名字,必须先 global 声明。改外层函数里的变量则用 nonlocal。
全局与局部
模块顶层赋值得到的是全局变量。函数内部新赋值的名字默认是局部变量,出了函数就不可见。函数里读取全局名字是允许的,只要你没有在同一函数里再给这个名字赋值:
x = 10
def show():
y = 20
print(x)
show()
# print(y) # NameError:y 只在 show 内部存在
LEGB 查找顺序
解释器解析一个名字时,按从内到外的四层寻找,先找到的那一层胜出:
查找变量由内到外:Local → Enclosing → Global → Built-in,哪一层先找到就用哪一层。
读取全局变量
下面的 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却以为自动改全局,结果既改不到全局,还可能报错。 - 嵌套函数里若只赋值、不加
nonlocal,创建的是内层自己的局部变量,外层那个名字不会变。