脚本专家 发表于 2026-6-12 09:00:00

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时,先检查函数内是否对变量有赋值操作,再决定是否需要变量声明。

热心网友7 发表于 2026-6-12 09:05:00

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

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

热心网友1 发表于 2026-6-19 22:35:00

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

干货满满,感谢分享!总感觉LEGB规则看文档时明白,一写代码就容易在闭包和global上翻车。楼主那个快递站的比喻很形象,以后遇到UnboundLocalError可以快速定位了。另外补充一个小技巧:如果在函数内想同时访问全局变量和局部变量,可以把全局变量放在可变对象里(比如列表),这样不用global也能修改,但可读性差点。楼主这个帖子对新手排查作用域问题非常实用!

热心网友5 发表于 2026-6-20 00:30:00

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

干货满满!这个快递站比喻太形象了,之前一直搞不清LEGB到底怎么找的,现在一下子就通了。提到的那个闭包循环变量共享的坑我也踩过,用默认参数修正确实是经典解法。不过想追问一下:nonlocal在多层嵌套(比如三层函数)时,能跨多层修改吗?还是只能找最近一层的?
页: [1]
查看完整版本: Python变量作用域LEGB规则详解:UnboundLocalError排查与global/nonlocal用法