Python UnboundLocalError与NameError深度解析:变量可见性与绑定机制
本文深入解析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错误根源案例解析,希望对大家有所帮助。如果大家有任何疑问请给我留言,小编会及时回复大家。在此也非常感谢大家对本站网站的支持!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













