← 返回题目列表

Python 的 LEGB 作用域规则是什么?

高频 中等 第 4 / 21 题 更新于 2026/07/27
LEGB作用域globalnonlocal

简化版

LEGB 是 Python 查找变量名的顺序:Local 本地作用域、Enclosing 外层函数作用域、Global 模块全局作用域、Built-in 内置作用域。global 用来声明使用模块级变量,nonlocal 用来声明使用外层函数变量。

详细版

Python 查找变量名时会按下面顺序:

  1. Local:当前函数内部。
  2. Enclosing:外层嵌套函数内部。
  3. Global:当前模块。
  4. Built-in:内置命名空间,比如 lenrange

示例:

x = "global"

def outer():
    x = "enclosing"

    def inner():
        x = "local"
        print(x)

    inner()

outer()  # local

如果内部函数没有自己的 x,会继续去外层找。

globalnonlocal 不是查找变量用的,而是告诉 Python:赋值时不要创建新的局部变量。

count = 0

def inc():
    global count
    count += 1
def outer():
    count = 0

    def inc():
        nonlocal count
        count += 1
        return count

    return inc

完整版教学

一、什么是命名空间

Python 里的变量名存在命名空间中。命名空间可以理解成一个“名字到对象的映射表”。

比如模块里写:

name = "Python"

模块命名空间里就有一个 name,它绑定到字符串对象 "Python"

函数调用时也会创建自己的局部命名空间:

def greet():
    message = "hello"

message 只存在于这次函数调用的局部空间里,函数执行结束后通常就不可访问了。

二、LEGB 查找顺序

看这个例子:

x = "G"

def outer():
    x = "E"

    def inner():
        print(x)

    inner()

outer()

inner 里没有定义 x,Python 会去外层函数 outer 找,所以输出 E

如果外层也没有:

x = "G"

def outer():
    def inner():
        print(x)
    inner()

它会继续到模块全局找,输出 G

如果模块全局也没有,就会去内置作用域找。比如:

print(len([1, 2, 3]))

len 不是你定义的名字,而是内置命名空间里的函数。

三、赋值会让名字变成本地变量

一个很高频的坑:

count = 0

def inc():
    count += 1
    return count

inc()

这会报 UnboundLocalError。原因是 Python 看到函数内部有 count += 1,会把 count 判断为局部变量;但执行 count + 1 时,局部变量 count 还没有值。

等价理解:

count = count + 1

右边想读局部 count,但局部 count 尚未绑定。

四、global 的作用

如果你明确想修改模块级变量,可以写:

count = 0

def inc():
    global count
    count += 1
    return count

global count 表示函数内部对 count 的赋值,作用在模块全局变量上。

但工程里不要轻易依赖全局可变状态。全局变量会让函数变得难测试,也容易在并发或多模块调用时造成隐蔽问题。

更好的方式通常是返回新值:

def inc(count):
    return count + 1

或者把状态封装到对象里。

五、nonlocal 的作用

nonlocal 用于嵌套函数,表示使用外层函数作用域里的变量:

def make_counter():
    count = 0

    def inc():
        nonlocal count
        count += 1
        return count

    return inc

这里 count 不在 inc 的本地作用域,也不是模块全局变量,而是在外层函数 make_counter 的作用域中,所以要用 nonlocal

如果不用 nonlocalcount += 1 会被当成局部赋值,导致 UnboundLocalError

六、内置名字也可能被遮蔽

不要写这种代码:

list = [1, 2, 3]
print(list("abc"))

你把内置函数 list 遮蔽成了一个列表对象,后面再想调用 list() 就会失败。

常见不要随便当变量名的内置名字包括:

  • list
  • dict
  • set
  • str
  • id
  • type
  • sum

七、常见误区与追问

易错点:读名字按 LEGB 找,写名字默认变成本地绑定。globalnonlocal 改的不是查找顺序,而是“赋值应该落到哪一层作用域”。

  • 误区:global 表示全项目全局。 Python 的 global 指当前模块命名空间,不是跨模块共享的全局宇宙;其他模块仍要通过导入模块对象或显式传参访问。
  • 追问:为什么 count += 1 会触发 UnboundLocalError 增强赋值需要先读再写,编译阶段又因为函数内有赋值把 count 判为局部变量,执行时局部 count 还没绑定,所以报错。
  • 误区:nonlocal 可以引用全局变量。 nonlocal 只能绑定到最近的外层函数作用域变量;如果外层函数没有这个名字,会直接语法报错,模块级变量应使用 global
  • 追问:内置函数被遮蔽后怎么恢复? 删除或改名当前作用域里的同名变量即可;临时访问也可以通过 import builtins 后使用 builtins.listbuiltins.sum
  • 误区:查找不到名字时一定是 NameError 如果名字被判定为局部变量但读取时尚未赋值,常见错误是 UnboundLocalError,它是 NameError 的子类。
  • 追问:闭包里只读取外层变量需要 nonlocal 吗? 不需要;只有在内部函数里要对外层变量重新绑定,例如 count += 1count = count + 1,才需要 nonlocal

八、加强记忆

LEGB 就是“先局部,再外层,再全局,最后内置”。读变量按 LEGB 查找,赋值却会默认创建局部变量;如果要改模块变量用 global,要改外层函数变量用 nonlocal。这道题的核心不是背缩写,而是能解释为什么 count += 1 会报 UnboundLocalError