Python变量作用域LEGB规则详解:UnboundLocalError排查与global/nonlocal用法
写Python时,你是不是也遇到过这样的问题:明明在函数外部定义了一个变量,函数内部读它没问题,一修改就报UnboundLocalError?下面通过一个实际案例,彻底讲清Python的变量作用域机制,以及如何正确使用global和nonlocal。一、一个典型的UnboundLocalError
朋友写了段统计数字总和的代码:
total = 0
def process_numbers():
while True:
num = input("请输入数字(输入q退出):")
if num == 'q':
break
total = total + int(num)
print(f"当前总和:{total}")
process_numbers()
print(f"最终总和:{total}")
运行报错:UnboundLocalError: local variable 'total' referenced before assignment。
明明total定义在函数外,为什么报“局部变量未定义”?换一个只读的例子却正常:
count = 10
def show():
print(count)
show()# 输出10
原因在于:当函数内有对变量的赋值操作时,Python会默认该变量是局部变量,而不会去访问全局作用域。
二、作用域本质:快递站比喻
把Python的作用域想象成三级快递存放点:
- 局部(你的家门口):只有当前函数能访问。
- 嵌套(小区快递柜):外层函数变量可以被内层函数访问(但不可直接修改)。
- 全局(城市物流中心):整个文件都能访问。
- 内置(全国通用):Python自带的函数和类型,如len、print。
这就是LEGB查找顺序:Local → Enclosing → Global → Built-in。变量名查找时,从内向外依次尝试,找到第一个即停止,否则报NameError。
三、四个作用域详解
1. 局部作用域(Local)
函数内定义的变量只属于该函数:
def greet():
message = "你好"
print(message)
greet()# 输出:你好
print(message)# NameError
函数内可以访问全局变量,但外部不能访问函数内部的变量。
2. 全局作用域(Global)
函数外定义的变量,函数内可以读取,但修改需要global声明:
counter = 0
def increment():
global counter
counter = counter + 1
increment()
print(counter)# 1
没有global时,counter = counter + 1会被Python解释为创建新局部变量,导致右边counter未定义。
3. 嵌套作用域(Enclosing)
函数内部再定义函数,内层函数可以访问外层函数的变量,修改需用nonlocal:
def outer():
x = 10
def inner():
nonlocal x
x = 20
inner()
print(x)# 20
outer()
nonlocal的作用范围是外层函数的局部变量,而非全局。
4. 内置作用域(Built-in)
Python内置的名称(如len、int)默认可用。但注意不要覆盖:
len = 10
print(len("hello"))# TypeError: 'int' object is not callable
四、四个常见坑
坑1:if/for不创建作用域
if True:
x = 10
print(x)# 10,x仍存在
for i in range(5):
pass
print(i)# 4,i泄漏到外部
只有函数定义会创建新作用域。
坑2:忘记global导致的“静默失败”
name = "张三"
def change():
name = "李四"# 创建局部变量,外层不变
change()
print(name)# 张三
不报错,但结果不符合预期。
坑3:列表推导式变量泄漏(Python 2 vs 3)
x = 10
squares =
print(x)# Python 3中输出10,Python 2中输出4
Python 3已修复,但迁移旧代码时需留意。
坑4:闭包中循环变量共享
funcs = []
for i in range(3):
funcs.append(lambda: i)
for f in funcs:
print(f())# 输出2 2 2
所有lambda共享同一个i的最终值。修复:将i作为默认参数:
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)
for f in funcs:
print(f())# 输出0 1 2
五、实战:三个函数覆盖LEGB
1. 只读全局:不需要global
config = "production"
def get_env():
return config
print(get_env())# production
2. 修改全局:加global
mode = "read"
def switch_to_write():
global mode
mode = "write"
switch_to_write()
print(mode)# write
3. 闭包计数:使用nonlocal
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter())# 1
print(counter())# 2
print(counter())# 3
闭包让内层函数“记住”外层变量,即使外层函数已执行完毕。
六、快速判断变量作用域的方法
- 函数内有无对该变量的赋值?
- 无 → 变量从外层/全局查找
- 有 → 变量被认为是局部(除非用global或nonlocal声明)
- 如果局部变量在赋值前被引用 → UnboundLocalError
七、回到开头的bug
总和的修复只需在函数内部加上global total:
def process_numbers():
global total
# 其余代码不变
现在total被当作全局变量,累加正常。
掌握LEGB规则和global/nonlocal的用法,就能避免大多数作用域引发的错误。当遇到UnboundLocalError时,先检查函数内是否对变量有赋值操作,再决定是否需要变量声明。
Re: Python变量作用域LEGB规则详解:UnboundLocalError排查与global/nonlocal用法
感谢分享,讲得非常清楚!快递站的比喻很形象,LEGB 的顺序一下子就记住了。我之前也踩过坑3和坑4,特别是闭包循环变量那个,用默认参数解决确实简洁。另外提醒一下,Python 3 里列表推导式也有独立作用域了,从你举的例子看确实修复了,这个细节对迁移老代码很有帮助。Re: Python变量作用域LEGB规则详解:UnboundLocalError排查与global/nonlocal用法
干货满满,感谢分享!总感觉LEGB规则看文档时明白,一写代码就容易在闭包和global上翻车。楼主那个快递站的比喻很形象,以后遇到UnboundLocalError可以快速定位了。另外补充一个小技巧:如果在函数内想同时访问全局变量和局部变量,可以把全局变量放在可变对象里(比如列表),这样不用global也能修改,但可读性差点。楼主这个帖子对新手排查作用域问题非常实用!Re: Python变量作用域LEGB规则详解:UnboundLocalError排查与global/nonlocal用法
干货满满!这个快递站比喻太形象了,之前一直搞不清LEGB到底怎么找的,现在一下子就通了。提到的那个闭包循环变量共享的坑我也踩过,用默认参数修正确实是经典解法。不过想追问一下:nonlocal在多层嵌套(比如三层函数)时,能跨多层修改吗?还是只能找最近一层的?
页:
[1]