明明在赋值前读取,为什么还会报 UnboundLocalError?global 与 nonlocal 深挖
2026/9/9 12:15:21 网站建设 项目流程

「Python 进阶之路」系列 Day32

写在前面

Day04 讲闭包与作用域时提过nonlocal,今天作为面试高频题串讲的第二篇,把global/nonlocal单独拎出来深挖——重点讲一个经常让人摸不着头脑的报错:明明代码是"先读取、后赋值",为什么 Python 会说这个变量"还没被赋值"?


一、是什么:两个改写外层变量的关键字

  • global:声明函数内某个名字指向模块级别的全局作用域,赋值会直接修改全局变量,而不是创建局部变量
  • nonlocal:声明函数内某个名字指向最近一层外层函数的作用域(不是全局),常用于嵌套函数里修改闭包变量
counter=0defincrement():globalcounter counter+=1increment()increment()print(counter)# 2

二、为什么:编译期就决定了变量归属

Python 编译函数体时,会静态扫描整个函数内部——只要函数体内任意位置对某个名字有赋值动作,这个名字在整个函数体内都会被当成局部变量,不管这行赋值实际会不会被执行到、也不管它出现在读取语句之前还是之后。

global/nonlocal就是用来打破这条默认规则的:显式声明"这个名字不要当局部变量,去外层/全局作用域找"。


三、怎么用

1. UnboundLocalError经典陷阱

不加global,函数内对同名变量赋值只会创建一个新的局部变量,完全不影响外部:

x=100defmodify_x():x=200print("函数内部x:",x)modify_x()print("函数外部x:",x)# 100,没被修改

更容易踩的坑是:只要函数体内某处有赋值语句,哪怕赋值语句写在读取之后,Python 也会在编译期把这个名字整体标记为局部变量:

y=100defbad_read():print(y)# 想读全局变量yy=200# 但这行赋值让Python在编译期就把y标记成了局部变量bad_read()# UnboundLocalError: cannot access local variable 'y' where it is not associated with a value

print(y)这一行看起来应该能读到全局的y(毕竟这行代码执行时,下面那行赋值还没跑到),但 Python 判断"y 是不是局部变量"是整个函数体一次性静态扫描的结果,不是按代码执行顺序动态判断的——只要函数体内有y = ...,整个函数体从头到尾的y都算局部变量,print(y)这行读取的是"尚未被赋值的局部变量",直接报错。

2. 关键区别:global能自动创建,nonlocal不行

存在

不存在

global声明

直接绑定模块的全局作用域
不存在就自动创建

nonlocal声明

最近一层外层函数是否已存在该变量

绑定到那一层

直接SyntaxError
不会继续往外找

global声明的变量即使全局作用域里一开始压根不存在,也完全没问题,调用函数时会自动创建:

defcreate_new_global():globalbrand_new_var brand_new_var="我是运行时被global创建出来的"print("brand_new_var"inglobals())# Falsecreate_new_global()print("brand_new_var"inglobals())# True

nonlocal则要求"最近一层外层函数作用域"必须已经存在这个变量,否则在编译期直接报SyntaxError,不会等到运行时才发现:

defouter2():definner2():nonlocalnot_exist# outer2根本没有not_exist这个变量not_exist=1inner2()# SyntaxError: no binding for nonlocal 'not_exist' found

nonlocal也不会跳过"没有该变量"的中间层,继续往更外层或全局去找——它只认"最近一层",中间层没有就直接报错,哪怕全局作用域里恰好有同名变量:

mid_missing="全局的mid_missing"deflevel1():deflevel2():# level2这一层没有定义mid_missingdeflevel3():nonlocalmid_missing# level3声明nonlocal,但最近一层(level2)没有这个变量mid_missing="level3改的"level3()level2()level1()# SyntaxError: no binding for nonlocal 'mid_missing' found

只有当变量确实存在于最近一层外层函数时,nonlocal才能正常工作:

deflevel1_ok():mid_var="level1定义的"deflevel2_ok():deflevel3_ok():nonlocalmid_var# 一层层往外找,在level1_ok里找到了mid_var="level3改的"level3_ok()level2_ok()print(mid_var)level1_ok()# level3改的

四、面试追问

Q1:globalnonlocal分别解决什么问题?

global让函数内的赋值直接作用于模块级全局变量;nonlocal让嵌套函数内的赋值作用于最近一层外层函数的变量,两者都是为了打破"函数内赋值默认创建局部变量"这条默认规则。

Q2:为什么函数内明明在赋值语句之前读取变量,还是会报UnboundLocalError

Python 编译函数体时是整体静态扫描的:只要函数体内任意位置对某个名字有赋值,这个名字在整个函数体内都被当成局部变量,与赋值语句实际执行到没到、出现在读取语句前后完全无关。这个判定发生在编译期,而不是按代码实际执行顺序动态决定的。

Q3:globalnonlocal在"变量不存在"时的表现有什么关键区别?

global声明的变量哪怕全局作用域里原本不存在,调用时也能正常运行并自动创建;nonlocal要求最近一层外层函数作用域必须已经存在这个变量,否则在编译期直接抛出SyntaxError,不会等到运行时才暴露问题。

Q4:nonlocal会不会跳过没有该变量的中间层,去更外层或全局找?

不会。nonlocal只认"最近一层外层函数作用域",这一层没有就直接报错,哪怕更外层或全局恰好有同名变量,也不会继续往外查找。

Q5:多个函数共享同一个global变量,在实际项目里有什么隐患?

会造成隐式耦合——多个函数都能修改同一份全局状态,谁改了、什么时候改的很难追踪,尤其在多线程场景下还会引入竞态条件(Day17 讲过)。实际项目里更推荐把共享状态封装进类或者显式通过参数传递,而不是依赖分散在各处的全局变量。


下一篇预告

Day33 讲装饰器连环追问——围绕带参数的装饰器、多个装饰器叠加的执行顺序这些高频细节展开。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询