跳到主要内容

请介绍 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 对比​

globalnonlocal
作用引用/修改模块全局变量引用/修改外层函数变量
适用层级任意函数内嵌套函数内
影响范围整个模块最近的外层函数

变量遮蔽(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
  • 内层同名变量会遮蔽外层,避免与内置名称冲突
  • 函数内只要有赋值,该变量在整个函数内都被视为局部变量(编译期决定)