在 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,而非 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__'。