Python 的 LEGB 作用域规则是什么?
简化版
LEGB 是 Python 查找变量名的顺序:Local 本地作用域、Enclosing 外层函数作用域、Global 模块全局作用域、Built-in 内置作用域。global 用来声明使用模块级变量,nonlocal 用来声明使用外层函数变量。
详细版
Python 查找变量名时会按下面顺序:
- Local:当前函数内部。
- Enclosing:外层嵌套函数内部。
- Global:当前模块。
- Built-in:内置命名空间,比如
len、range。
示例:
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x)
inner()
outer() # local
如果内部函数没有自己的 x,会继续去外层找。
global 和 nonlocal 不是查找变量用的,而是告诉 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。
如果不用 nonlocal,count += 1 会被当成局部赋值,导致 UnboundLocalError。
六、内置名字也可能被遮蔽
不要写这种代码:
list = [1, 2, 3]
print(list("abc"))
你把内置函数 list 遮蔽成了一个列表对象,后面再想调用 list() 就会失败。
常见不要随便当变量名的内置名字包括:
listdictsetstridtypesum
七、常见误区与追问
易错点:读名字按 LEGB 找,写名字默认变成本地绑定。
global和nonlocal改的不是查找顺序,而是“赋值应该落到哪一层作用域”。
- 误区:
global表示全项目全局。 Python 的global指当前模块命名空间,不是跨模块共享的全局宇宙;其他模块仍要通过导入模块对象或显式传参访问。 - 追问:为什么
count += 1会触发UnboundLocalError? 增强赋值需要先读再写,编译阶段又因为函数内有赋值把count判为局部变量,执行时局部count还没绑定,所以报错。 - 误区:
nonlocal可以引用全局变量。nonlocal只能绑定到最近的外层函数作用域变量;如果外层函数没有这个名字,会直接语法报错,模块级变量应使用global。 - 追问:内置函数被遮蔽后怎么恢复? 删除或改名当前作用域里的同名变量即可;临时访问也可以通过
import builtins后使用builtins.list、builtins.sum。 - 误区:查找不到名字时一定是
NameError。 如果名字被判定为局部变量但读取时尚未赋值,常见错误是UnboundLocalError,它是NameError的子类。 - 追问:闭包里只读取外层变量需要
nonlocal吗? 不需要;只有在内部函数里要对外层变量重新绑定,例如count += 1、count = count + 1,才需要nonlocal。
八、加强记忆
LEGB 就是“先局部,再外层,再全局,最后内置”。读变量按 LEGB 查找,赋值却会默认创建局部变量;如果要改模块变量用 global,要改外层函数变量用 nonlocal。这道题的核心不是背缩写,而是能解释为什么 count += 1 会报 UnboundLocalError。