Python UnboundLocalError和NameError错误根源解析
如果代码风格相对而言不是那么的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
NameErrorexception is raised. If the name refers to a local variable that has not been bound, aUnboundLocalErrorexception is raised.UnboundLocalErroris a subclass ofNameError.
大概意思是:
如果引用了某个变量,但是变量名没有找到,该类型的错误就是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必须要可见而且是绑定的。
先了解一下几个概念:
- code block:作为一个单元(Unit)被执行的一段python程序文本。例如一个模块、函数体和类的定义等。
- scope:在一个code block中定义name的可见性;
- block’s environment:对于一个code block,其所有scope中可见的name的集合构成block的环境。
- bind name:下面的操作均可视为绑定操作
- 函数的形参
- import声明
- 类和函数的定义
- 赋值操作
- for循环首标
- 异常捕获中相关的赋值变量
- local variable:如果name在一个block中被绑定,该变量便是该block的一个local variable。
- global variable:如果name在一个module中被绑定,该变量便称为一个global variable。
- 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学习笔记014——错误和异常
Python有两种错误很容易辨认:语法错误和异常. 1 什么是语法错误 Python 的语法错误或者称之为解析错,是初学者经常碰到的,如下实例 if i>4 print("if语句输出 ...
- Python程序的常见错误(收集篇)
关于Python Python是一门解释性的,面向对象的,并具有动态语义的高级编程语言.它高级的内置数据结构,结合其动态类型和动态绑定的特性,使得它在快速应用程序开发(Rapid Applicatio ...
- Python学习 Part6:错误和异常
Python学习 Part6:错误和异常 两种不同类型的错误:语法错误和异常 1. 语法错误 语法错误,也被称作解析错误: >>> while True print('Hello w ...
- Python学习笔记七-错误和异常
程序员总是和各种错误打交道,学习如何识别并正确的处理程序错误是很有必要的. 7.1错误和异常 1.错误 从软件方面来看,错误分为语法错误和逻辑错误两种.这两种错误都将导致程序无法正常进行下去,当Pyt ...
- Python常见经典 python中if __name__ == '__main__': 的解析
当你打开一个.py文件时,经常会在代码的最下面看到if __name__ == '__main__':,现在就来介 绍一下它的作用. 模块是对象,并且所有的模块都有一个内置属性 __name__.一个 ...
- Python 新手常犯错误
Python 新手常犯错误(第二部分) 转发自:http://blog.jobbole.com/43826/ 作用域 在这篇文章里,我们来关注作用域在Python被误用的地方.通常,当我们定义了一个全 ...
- python实现布隆过滤器及原理解析
python实现布隆过滤器及原理解析 布隆过滤器( BloomFilter )是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地 ...
- python基础语法_10错误与异常
Python有两种错误很容易辨认:语法错误和异常. 语法错误 Python 的语法错误或者称之为解析错,是初学者经常碰到的,如下实例 异常 即便Python程序的语法是正确的,在运行它的时候,也有可能 ...
- sqlalchemy mark-deleted 和 python 多继承下的方法解析顺序 MRO
sqlalchemy mark-deleted 和 python 多继承下的方法解析顺序 MRO 今天在弄一个 sqlalchemy 的数据库基类的时候,遇到了跟多继承相关的一个小问题,因此顺便看了一 ...
随机推荐
- shell基础及变量
一 Shell概述 1.Shell的作用——命令解释器,“翻译官” shell作为一个人机接口,用于解释用户输入的命令,将命令解释为Linux内核可以执行的2进制代码,并将执行的结果返回在标准终端上. ...
- ES8 async/await语法
Async/await的主要益处是可以避免回调地狱(callback hell)问题 Chromium JavaScript引擎 从v5.5开始支持async/await功能,Chromium Jav ...
- [LeetCode] Positions of Large Groups 大群组的位置
In a string S of lowercase letters, these letters form consecutive groups of the same character. For ...
- js原型与继承
demofunction Fun(){} var foo = new Fun();foo.__proto__ === Fun.prototype 摘要 1.js本身不提供类实现,es6引入了class ...
- 【RL-TCPnet网络教程】第3章 初学RL-TCPnet的准备工作及其快速上手
第3章 初学RL-TCPnet的准备工作及其快速上手 俗话说万事开头难,学习一门新的知识,难的往往不是知识本身,而是如何快速上手,需要什么资料和开发环境.一旦上手后,深入的学习就相对容易些 ...
- 【安富莱原创开源应用第1期】花式玩转网络摄像头之TCP上位机软件实现,高端大气上档次,速度2MB/S,华丽丽的界面效果
说明:1.例子是两年前做的,一直没有顾上整理出来,今天特地整理出来,开源出来给大家玩.2.上位机是emWin模拟器开发的,大家估计很难猜到,所以你会emWin话的,就可以轻松制作上位机.做些通信和控制 ...
- 使用Java 线程池的利弊及JDK自带六种创建线程池的方法
1. 为什么使用线程池 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到达服务器,这种方式可能是通过网络协 ...
- 剑指Offer全解
二维数组中的查找 描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中 ...
- javascript时间戳与日期格式之间的互转
1. 将时间戳转换成日期格式 // 简单的一句代码 var date = new Date(时间戳); //获取一个时间对象 /** 1. 下面是获取时间日期的方法,需要什么样的格式自己拼接起来就好了 ...
- 一个老程序员是如何手写Spring MVC的
人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...