Python 命名空间与作用域
摘要
namespace (命名空间)是从名称到对象的映射。现在,大多数命名空间都使用 Python 字典实现,但除非涉及到性能优化,我们一般不会关注这方面的事情,而且将来也可能会改变这种方式。
- 命名空间的例子:
- 内置名称集合(包括
abs()函数以及内置异常的名称等) - 一个模块的全局名称
- 一个函数调用中的局部名称
- ······
- 内置名称集合(包括
- 命名空间是在不同时刻创建的,且拥有不同的生命周期。
一个命名空间的 作用域 是 Python 代码中的一段文本区域,从这个区域可直接访问该命名空间。
总结:命名空间 = 装变量的 " 盒子 ",作用域 = 你能用到这个盒子的 " 范围 "
查找顺序(从内到外)
def outer():
x = "我是外层函数的变量" # ← 第2层:闭包作用域(nonlocal)
def inner():
x = "我是局部变量" # ← 第1层:最内层(局部)
print(x) # 先找这里
inner()
# 第3层:全局(模块级别)
# 第4层:内置(如 len, print)
| 规则 | 说明 | 代码示例 |
|---|---|---|
| LEGB 查找 | Local → Enclosing → Global → Built-in | print(x) 按这个顺序找 |
| 赋值即局部 | 函数内赋值 x = ...,x 就是局部变量 | 见下方陷阱 ⚠️ |
| global/nonlocal | 声明后才可修改外层变量 | global x 或 nonlocal x |
经典陷阱:UnboundLocalError
x = 10 # 全局
def foo():
print(x) # 报错!因为下面有赋值,Python 认定 x 是局部变量
x = 20 # 赋值让 x 变成局部,但上面已经用了
foo()
解决方法:
def foo():
global x # 声明:我要用全局的 x
print(x) # ✓ 正常
x = 20
global 与 nonlocal 的使用
Python 变量声明即赋值,赋值即为局部变量,如果想要在一个作用域内使用外部的变量(外层变量和全局变量),就可以声明为**global/nonlocal**,两者的差别使用一个例子即可观察清楚
def outer():
x = "outer"
def middle():
# middle 自己没有 x
def inner():
nonlocal x # ← 跳过 middle,直接找到 outer 的 x
x = "modified"
inner()
print("middle:", x) # → "modified"(middle 也能看到变化)
middle()
print("outer:", x) # → "modified"
outer()
nonlocal会逐层向外查找**,找到最近的外层变量,但是无法跨到全局global会从全局变量进行捕获,如果全局变量没有,则会创建新的全局变量
| 声明 | 目标 | 作用 | 未声明时的行为 |
|---|---|---|---|
| (默认) | 局部 | 函数内赋值即创建局部变量 | 赋值即局部,读取会报错(如果先读后写) |
global | 全局 | 强制使用模块级变量 | 创建新的全局变量(如果不存在) |
nonlocal | 闭包 | 强制使用外层函数的变量 | 逐层向外找最近的定义,跳过无此变量的层 |