请介绍 Python 中变量的作用域?
LEGB 规则
Python 使用 LEGB 规则按顺序查找变量,找到即停止:
L → Local 局部作用域:函数内部定义的变量
E → Enclosing 闭包作用域:外层嵌套函数的变量
G → Global 全局作用域:模块顶层定义的变量
B → Built-in 内置作用域:Python 内置名称(len、print 等)
Built-in
└── Global(模块)
└── Enclosing(外层函数)
└── Local(当前函数) ← 优先查找
四个作用域示例
x = "global" # Global
def outer():
x = "enclosing" # Enclosing
def inner():
x = "local" # Local
print(x) # local ← 优先找 Local
inner()
print(x) # enclosing
outer()
print(x) # global
print(len) # <built-in function len> ← Built-in
global 关键字
在函数内部修改全局变量时,需要用 global 声明:
count = 0
def increment():
global count # 声明使用全局变量
count += 1
increment()
increment()
print(count) # 2
不加 global 直接赋值,Python 会把它当作局部变量:
count = 0
def increment():
count += 1 # ❌ UnboundLocalError: local variable 'count' referenced before assignment
increment()
nonlocal 关键字
在闭包内部修改外层函数的变量时,需要用 nonlocal:
def outer():
count = 0
def inner():
nonlocal count # 声明使用外层变量
count += 1
return count
print(inner()) # 1
print(inner()) # 2
outer()
global vs nonlocal 对比
global | nonlocal | |
|---|---|---|
| 作用 | 引用/修改模块全局变量 | 引用/修改外层函数变量 |
| 适用层级 | 任意函数内 | 嵌套函数内 |
| 影响范围 | 整个模块 | 最近的外层函数 |
变量遮蔽(Shadowing)
内层作用域的同名变量会遮蔽外层变量:
len = 10 # 遮蔽了内置 len 函数
print(len) # 10
print(len([1,2])) # ❌ TypeError: 'int' object is not callable
del len # 删除后恢复内置 len
print(len([1,2])) # 2
避免用内置名称(list、dict、id、type 等)作为变量名。
作用域查找的常见陷阱
陷阱 1:函数内的赋值使变量变为局部变量
x = 10
def foo():
print(x) # ❌ UnboundLocalError
x = 20 # 这一行让 Python 认为 x 是局部变量
foo()
Python 在编译阶段就确定了变量是否为局部变量(只要函数内有赋值语句),而非运行时。
陷阱 2:循环变量泄漏到外层(仅列表推导式在 Python 2 有此问题)
# Python 3:for 循环变量会泄漏到外层作用域
for i in range(3):
pass
print(i) # 2 ← i 在循环后仍可访问
# 列表推导式不会泄漏(Python 3 有独立作用域)
result = [x for x in range(3)]
print(x) # ❌ NameError: name 'x' is not defined
查看作用域内的变量
x = 1
def foo():
y = 2
print(locals()) # {'y': 2} ← 局部变量
print(globals()) # {'x': 1, ...} ← 全局变量(含模块级所有名称)
foo()
总结
- Python 按 LEGB 顺序查找变量:Local → Enclosing → Global → Built-in
- 函数内只读外层变量无需声明;修改全局变量用
global,修改外层函数变量用nonlocal - 内层同名变量会遮蔽外层,避免与内置名称冲突
- 函数内只要有赋值,该变量在整个函数内都被视为局部变量(编译期决定)