查看: 197|回复: 1

Python变量作用域LEGB规则详解:UnboundLocalError排查与global/nonlocal用法

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
写Python时,你是不是也遇到过这样的问题:明明在函数外部定义了一个变量,函数内部读它没问题,一修改就报UnboundLocalError?下面通过一个实际案例,彻底讲清Python的变量作用域机制,以及如何正确使用global和nonlocal。

一、一个典型的UnboundLocalError
朋友写了段统计数字总和的代码:
  1. total = 0
  2. def process_numbers():
  3.     while True:
  4.         num = input("请输入数字(输入q退出):")
  5.         if num == 'q':
  6.             break
  7.         total = total + int(num)
  8.     print(f"当前总和:{total}")
  9. process_numbers()
  10. print(f"最终总和:{total}")
复制代码
运行报错:UnboundLocalError: local variable 'total' referenced before assignment。
明明total定义在函数外,为什么报“局部变量未定义”?换一个只读的例子却正常:
  1. count = 10
  2. def show():
  3.     print(count)
  4. show()  # 输出10
复制代码
原因在于:当函数内有对变量的赋值操作时,Python会默认该变量是局部变量,而不会去访问全局作用域。

二、作用域本质:快递站比喻
把Python的作用域想象成三级快递存放点:
- 局部(你的家门口):只有当前函数能访问。
- 嵌套(小区快递柜):外层函数变量可以被内层函数访问(但不可直接修改)。
- 全局(城市物流中心):整个文件都能访问。
- 内置(全国通用):Python自带的函数和类型,如len、print。
这就是LEGB查找顺序:Local → Enclosing → Global → Built-in。变量名查找时,从内向外依次尝试,找到第一个即停止,否则报NameError。

三、四个作用域详解
1. 局部作用域(Local)
函数内定义的变量只属于该函数:
  1. def greet():
  2.     message = "你好"
  3.     print(message)
  4. greet()  # 输出:你好
  5. print(message)  # NameError
复制代码
函数内可以访问全局变量,但外部不能访问函数内部的变量。

2. 全局作用域(Global)
函数外定义的变量,函数内可以读取,但修改需要global声明:
  1. counter = 0
  2. def increment():
  3.     global counter
  4.     counter = counter + 1
  5. increment()
  6. print(counter)  # 1
复制代码
没有global时,counter = counter + 1会被Python解释为创建新局部变量,导致右边counter未定义。

3. 嵌套作用域(Enclosing)
函数内部再定义函数,内层函数可以访问外层函数的变量,修改需用nonlocal:
  1. def outer():
  2.     x = 10
  3.     def inner():
  4.         nonlocal x
  5.         x = 20
  6.     inner()
  7.     print(x)  # 20
  8. outer()
复制代码
nonlocal的作用范围是外层函数的局部变量,而非全局。

4. 内置作用域(Built-in)
Python内置的名称(如len、int)默认可用。但注意不要覆盖:
  1. len = 10
  2. print(len("hello"))  # TypeError: 'int' object is not callable
复制代码

四、四个常见坑
坑1:if/for不创建作用域
  1. if True:
  2.     x = 10
  3. print(x)  # 10,x仍存在
  4. for i in range(5):
  5.     pass
  6. print(i)  # 4,i泄漏到外部
复制代码
只有函数定义会创建新作用域。

坑2:忘记global导致的“静默失败”
  1. name = "张三"
  2. def change():
  3.     name = "李四"  # 创建局部变量,外层不变
  4. change()
  5. print(name)  # 张三
复制代码
不报错,但结果不符合预期。

坑3:列表推导式变量泄漏(Python 2 vs 3)
  1. x = 10
  2. squares = [x**2 for x in range(5)]
  3. print(x)  # Python 3中输出10,Python 2中输出4
复制代码
Python 3已修复,但迁移旧代码时需留意。

坑4:闭包中循环变量共享
  1. funcs = []
  2. for i in range(3):
  3.     funcs.append(lambda: i)
  4. for f in funcs:
  5.     print(f())  # 输出2 2 2
复制代码
所有lambda共享同一个i的最终值。修复:将i作为默认参数:
  1. funcs = []
  2. for i in range(3):
  3.     funcs.append(lambda i=i: i)
  4. for f in funcs:
  5.     print(f())  # 输出0 1 2
复制代码

五、实战:三个函数覆盖LEGB
1. 只读全局:不需要global
  1. config = "production"
  2. def get_env():
  3.     return config
  4. print(get_env())  # production
复制代码
2. 修改全局:加global
  1. mode = "read"
  2. def switch_to_write():
  3.     global mode
  4.     mode = "write"
  5. switch_to_write()
  6. print(mode)  # write
复制代码
3. 闭包计数:使用nonlocal
  1. def make_counter():
  2.     count = 0
  3.     def increment():
  4.         nonlocal count
  5.         count += 1
  6.         return count
  7.     return increment
  8. counter = make_counter()
  9. print(counter())  # 1
  10. print(counter())  # 2
  11. print(counter())  # 3
复制代码
闭包让内层函数“记住”外层变量,即使外层函数已执行完毕。

六、快速判断变量作用域的方法
- 函数内有无对该变量的赋值?
  - 无 → 变量从外层/全局查找
  - 有 → 变量被认为是局部(除非用global或nonlocal声明)
- 如果局部变量在赋值前被引用 → UnboundLocalError

七、回到开头的bug
总和的修复只需在函数内部加上global total:
  1. def process_numbers():
  2.     global total
  3.     # 其余代码不变
复制代码
现在total被当作全局变量,累加正常。

掌握LEGB规则和global/nonlocal的用法,就能避免大多数作用域引发的错误。当遇到UnboundLocalError时,先检查函数内是否对变量有赋值操作,再决定是否需要变量声明。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python变量作用域LEGB规则详解:UnboundLocalError排查与global/nonlocal用法

感谢分享,讲得非常清楚!快递站的比喻很形象,LEGB 的顺序一下子就记住了。我之前也踩过坑3和坑4,特别是闭包循环变量那个,用默认参数解决确实简洁。另外提醒一下,Python 3 里列表推导式也有独立作用域了,从你举的例子看确实修复了,这个细节对迁移老代码很有帮助。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-12 11:58 , Processed in 0.034194 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部