Python UnboundLocalError与NameError深度解析:变量可见性与绑定机制

周叙
周叙
发布时间:2026-10-13| 4人浏览过
原创

本文深入解析Python中UnboundLocalError和NameError的根源。通过三个典型案例,揭示变量在code block中可见性与绑定操作的关系。解释为何赋值前引用会报错,以及free variable在闭包中的行为,帮助开发者理解Python变量查找规则。

如果代码风格不够Pythonic,或许很少遇到这类错误。当然,这并不意味着不鼓励使用Python语言的高级特性。遇到此类错误,通常表明对Python变量引用机制存在误解,而深入理解这些概念对于掌握Python至关重要,这也是本文撰写的初衷。

本文为深入理解闭包概念做铺垫,后续将发布详细探讨闭包的博文,敬请关注。

1.案例分析

在梳理闭包相关概念时,经常发现UnboundLocalError和NameError这两个错误。初次遇到时,开发者往往感到困惑,不知从何下手。

1.1 案例一:

def outer_func():
  loc_var = "local variable"
  def inner_func():
    loc_var += " in inner func"
    return loc_var
  return inner_func
clo_func = outer_func()
clo_func()

错误提示:

Traceback (most recent call last): File "G:\Project Files\Python Test\Main.py", line 238, in <module> clo_func() File "G:\Project Files\Python Test\Main.py", line 233, in inner_func loc_var += " in inner func" UnboundLocalError: local variable 'loc_var' referenced before assignment

1.2 案例二:

 def get_select_desc(name, flag, is_format = True):
   if flag:
     sel_res = 'Do select name = %s' % name
  return sel_res if is_format else name 
 get_select_desc('Error', False, True)

错误提示:

Traceback (most recent call last): File "G:\Project Files\Python Test\Main.py", line 247, in <module> get_select_desc('Error', False, True) File "G:\Project Files\Python Test\Main.py", line 245, in get_select_desc return sel_res if is_format else name UnboundLocalError: local variable 'sel_res' referenced before assignment

1.3 案例三:

def outer_func(out_flag):
  if out_flag:
    loc_var1 = 'local variable with flag'
  else:
    loc_var2 = 'local variable without flag'
  def inner_func(in_flag):
    return loc_var1 if in_flag else loc_var2
  return inner_func

clo_func = outer_func(True)
print clo_func(False)

错误提示:

Traceback (most recent call last): File "G:\Project Files\Python Test\Main.py", line 260, in <module> print clo_func(False) File "G:\Project Files\Python Test\Main.py", line 256, in inner_func return loc_var1 if in_flag else loc_var2 NameError: free variable 'loc_var2' referenced before assignment in enclosing scope

上述三个例子可能显得刻意,但实际开发中类似的错误代码往往能在这些案例中找到影子。这里仅用于说明相关概念,无需过度纠结例子本身的合理性。

2.错误原因

由于Python中没有变量、函数或类的声明概念,若按C或C++的习惯编写Python,很难发现错误的根源。

首先查看此类错误的官方解释:

When a name is not found at all, a NameError exception is raised. If the name refers to a local variable that has not been bound, a UnboundLocalError exception is raised. UnboundLocalError is a subclass of NameError.

大致意思是:

如果引用了某个变量但找不到该名称,会引发NameError。如果该名称指向一个尚未绑定的局部变量,则引发NameError的子类UnboundLocalError。

以下这种NameError类型的错误相对容易理解:

 my_function()
 def my_function():
   pass

若Python解释器在遇到def my_function()时才绑定到my_function,此时my_function表示内存中函数执行的入口。因此在此之前使用my_function均会引发NameError。

那么,在上述案例中,变量引用前均有赋值操作(可视为一种绑定操作,后续会详述),为何引用时会出错?定义也可判断可见性。

如果是因为赋值操作未执行,为何该变量名在局部命名空间中可见?(若不可见,会报NameError: global name 'xxx' is not defined,根据UnboundLocalError定义也可判断可见性)。

问题究竟出在哪里?如何正确理解上述三个案例中的错误?

3. 可见性与绑定

为简化说明,此处不介绍命名空间与变量查找规则LGB相关的概念。

在C或C++中,只要声明并定义了一个变量或函数,即可直接使用。但在Python中,要引用一个name,该name必须可见且已绑定。

先了解几个概念:

1.code block:作为单元(Unit)被执行的一段Python程序文本,例如模块、函数体和类的定义等。 2.scope:在code block中定义name的可见性; 3.block's environment:对于code block,其所有scope中可见的name集合构成block的环境。 4.bind name:以下操作均视为绑定操作 •函数的形参

•import声明

•类和函数的定义

•赋值操作

•for循环首标

•异常捕获中相关的赋值变量

5.local variable:如果name在block中被绑定,该变量即为该block的local variable。 6.global variable:如果name在module中被绑定,该变量称为global variable。 7.free variable: 如果name在block中被引用,但未在该代码块中定义,则称为free variable。

Free variable是一个重要概念,在闭包中引用的父函数中的局部变量是free variable,且该free variable存储在cell对象中。这将在闭包相关文章中介绍。

scope在函数中具有可扩展性,但在类定义中不具有可扩展性。

分析整理如下:

通过上述概念介绍可知,只要变量在code block中有绑定操作,那么在code block的scope中即包含该变量。

也就是说,绑定操作决定了被绑定的name在当前scope(若是函数,也包括其中定义的scope)中可见,即使在name真正绑定之前。

这里存在一个问题:如果在绑定name操作之前引用了该name,即使该name可见,也会出现问题。

If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.

注意上述官方描述的第一句和最后一句话。

总结来说,在code block中,所有绑定操作中被绑定的name均视为local variable;但直到绑定操作执行后,才能真正引用该name。

有了这些概念,下面逐一分析上述三个案例。

4. 错误解析

4.1 案例一分析

在outer_func中定义了变量loc_var,因为赋值是绑定操作,所以loc_var具有可见性,并绑定到具体的字符串对象。

但在其中定义的函数inner_func中却无法引用,函数的scope不是可以扩展到其内定义的所有scope吗?

下面再看官方两段文字描述:

When a name is used in a code block, it is resolved using the nearest enclosing scope.

这段话告诉我们,当引用name时,它会在最近的scope中寻找被引用name的定义。显然loc_var += " in inner func"语句中的loc_var会先在内部函数inner_func中查找name loc_var。

该语句实际上等价于loc_var = loc_var + " in inner func",等号右边的loc_var变量首先被使用,但这里不会使用outer_func中定义的loc_var,因为在函数inner_func的scope中有loc_var的赋值操作,因此该变量在inner_func的scope中作为inner_func的local variable可见。

但要等到该语句执行完成,才能真正确定绑定loc_var。也就是说,此语句中我们使用了inner_func block中绑定前的local variable。根据上述错误类型定义,这是一个UnboundLocalError。

4.2 案例二分析

这个例子看起来有问题,但不知如何解释。

引用发生在绑定操作之后,变量应可正常引用。但问题在于赋值语句(绑定操作)不一定被执行。如果没有执行绑定操作,对变量的引用肯定会有问题,前文已解释。

但还有一个疑问:如果赋值语句未执行,变量在当前block中为何可见?

关于这个问题,其实可用上述一段话解释:The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.

只要有绑定操作(无论实际是否执行),被绑定的name即可作为local variable,即在当前block中可见。scanning text发生在代码执行前。

4.2 案例三分析

这个例子主要说明了一类对free variable引用的问题,同时也展示了free variable的使用。

在创建闭包inner_func时,loc_var1和loc_var2作为父函数outer_func中的两个local variable在其内部inner_func的scope中可见。返回闭包后,在闭包中引用outer_func中的local variable将称为free variable。

闭包中的free variable是否可引用,取决于它们是否已绑定到具体对象。

5. 引申案例

下面再看一个例子:

 import sys
 def add_path(new_path):
   path_list = sys.path
   if new_path not in path_list:
     import sys
     sys.path.append(new_path)
 add_path('./')

平时不经意间可能就会犯上述错误,这也是典型的UnboundLocalError错误。如果仔细阅读并理解上述分析过程,相信应能理解该错误的原因。若仍不清楚,请再阅读一遍:-)

总结

以上所述是小编给大家介绍的Python UnboundLocalError和NameError错误根源案例解析,希望对大家有所帮助。如果大家有任何疑问请给我留言,小编会及时回复大家。在此也非常感谢大家对本站网站的支持!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。