Python作用域规定一个名字在哪些代码区域可见。查找顺序是 LEGB:先局部 Local,再外层嵌套 Enclosing,再模块全局 Global,最后才是内置 Built-in。读全局变量通常可以直接读,但要在函数里改写全局名字,必须先 global 声明。改外层函数里的变量则用 nonlocal

全局与局部

模块顶层赋值得到的是全局变量。函数内部新赋值的名字默认是局部变量,出了函数就不可见。函数里读取全局名字是允许的,只要你没有在同一函数里再给这个名字赋值:

x = 10

def show():
    y = 20
    print(x)

show()
# print(y)   # NameError:y 只在 show 内部存在

LEGB 查找顺序

解释器解析一个名字时,按从内到外的四层寻找,先找到的那一层胜出:

查找顺序 Built-in 内置(print、len、range…) Global 全局(模块级) Enclosing 外层嵌套函数 Local 局部变量
查找变量由内到外: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,而不是 globalglobal 跳的是模块顶层,跳不到外层函数的局部作用域:

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,创建的是内层自己的局部变量,外层那个名字不会变。