Python 命名空间与变量作用域(LEGB)
本篇说明 命名空间、作用域,以及解释器如何按 LEGB 顺序解析名字。
什么是命名空间?
命名空间是把名字映射到对象的容器。Python 里「一切皆对象」,我们用名字引用对象,本质上就是在某个命名空间里做查找。
可以把它理解成「以名为键」的映射(概念上类似字典,但实现不一定是 dict):
# 示意:同一时刻不同命名空间里可以出现相同的名字,指向不同对象
# 全局: x -> 10
# 外层函数局部: x -> 20
# 内层函数局部: x -> 30重要:不同命名空间中可以重复使用同一个名字,它们互不冲突,各自绑定到可能不同的对象。
作用域与命名空间的关系
作用域是源码中的一段文本区域:在该区域内,某个命名空间里的名字可以直接使用(「可见」)。
一个命名空间对应「一块存放名字绑定的地方」;作用域描述的是「在哪儿能用到这些绑定」。
Python 中,类定义、函数定义、模块等会参与作用域的划分;普通的 for / try 语句块本身并不会再单独造一层函数式局部作用域——在函数里的 for 循环变量,通常仍属于该函数的局部作用域(与循环、分支写在同一函数中时共享同一套局部名字)。
(推导式、部分特殊构造有自己单独的名字规则,进阶时再区分即可。)
命名空间的常见分类与生命周期(概念)
| 类型 | 含义(概要) | 生命周期(概要) |
|---|---|---|
| 局部(Local) | 当前函数执行时的局部名字 | 函数调用进入时构建,返回后销毁(实现细节可理解为帧) |
| 闭包 / 外层(Enclosed) | 嵌套函数中,内层可见的「外层函数」的局部名字 | 随外层函数的局部生命周期 |
| 全局(Global) | 当前模块顶层的名字 | 模块被加载后存在,持续到解释器退出(通常) |
| 内置(Built-in) | len、int 等内置名所在的名字空间 | 解释器启动后存在 |
说明:「局部」一般指函数体内,而不是「每一个 for 或 try 各有一个独立命名空间」。
LEGB 解析规则
在函数体内部引用一个名字时,解释器按下面顺序查找,先找到即用,全部找不到则抛出 NameError:
Local(局部)→ Enclosed(闭包外层)→ Global(全局)→ Built-in(内置)
简称 LEGB。
>>> name = "global"
>>> def outer():
... name = "enclosed"
... def inner():
... name = "local"
... print(name)
... inner()
...
>>> outer()
local若在 inner 里不写 name = ...,则会向外层继续找,此处略;若需写入外层或全局的名字,要用 nonlocal / global(见下节)。
模块与属性的「命名空间」
import 会加载模块,模块有自己的全局命名空间;用 . 访问的是该对象的属性命名空间(例如模块里的函数、常量)。
>>> import math
>>> math.pi
3.141592653589793实例属性同理:
>>> obj = object()
>>> obj.__doc__
'The most base type'找不到名字会报错:
>>> not_defined_anywhere
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'not_defined_anywhere' is not defined综合示例:多个作用域中的同名 x
x = 10
print(f"x is {x}") # 全局
def outer():
x = 20
print(f"x is {x}") # outer 的局部
def inner():
x = 30
print(f"x is {x}") # inner 的局部
print(len("abc")) # len 来自 Built-in
inner()
outer()输出中三处 x 分别对应全局、outer 局部、inner 局部,互不混淆。
global 与 nonlocal
默认情况下,在函数内赋值的名字会被视为局部,除非声明:
global x:函数内的x指模块全局的x,赋值会改全局绑定。nonlocal x:用于嵌套函数,x指最近一层外层函数的局部变量,赋值会改外层函数的绑定。
滥用 global 会降低可读性;能靠参数与返回值传递时,通常更清晰。
小结
- 命名空间存「名字 → 对象」的映射;作用域决定在代码哪一段能直接访问哪些名字。
- 函数内找名字用 LEGB;找不到则
NameError。 - 嵌套函数才有典型的 Enclosed;内置名在最后兜底。
- 要在内层函数里修改外层或模块顶层的变量,需
nonlocal/global。