Python UnboundLocalError和NameError错误根源案例解析


Posted in Python onOctober 31, 2018

如果代码风格相对而言不是那么的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 相关文章推荐
wxpython学习笔记(推荐查看)
Jun 09 Python
python创建一个最简单http webserver服务器的方法
May 08 Python
Python实现将Excel转换为json的方法示例
Aug 05 Python
python学习教程之使用py2exe打包
Sep 24 Python
教你用一行Python代码实现并行任务(附代码)
Feb 02 Python
django 按时间范围查询数据库实例代码
Feb 11 Python
Python操作MySQL模拟银行转账
Mar 12 Python
python3实现磁盘空间监控
Jun 21 Python
Python3.5 处理文本txt,删除不需要的行方法
Dec 10 Python
Python之Django自动实现html代码(下拉框,数据选择)
Mar 13 Python
解决Python中报错TypeError: must be str, not bytes问题
Apr 07 Python
如何搭建pytorch环境的方法步骤
May 06 Python
python多进程控制学习小结
Oct 31 #Python
在Python中实现替换字符串中的子串的示例
Oct 31 #Python
python创建文件时去掉非法字符的方法
Oct 31 #Python
python3 中文乱码与默认编码格式设定方法
Oct 31 #Python
解决python中 f.write写入中文出错的问题
Oct 31 #Python
[原创]Python入门教程3. 列表基本操作【定义、运算、常用函数】
Oct 30 #Python
python将txt文件读入为np.array的方法
Oct 30 #Python
You might like
第三章 php操作符与控制结构代码
2011/12/30 PHP
php常用字符串查找函数strstr()与strpos()实例分析
2019/06/21 PHP
jqPlot Option配置对象详解
2009/07/25 Javascript
缓动函数requestAnimationFrame 更好的实现浏览器经动画
2012/12/07 Javascript
Javascript中的五种数据类型详解
2014/12/26 Javascript
JavaScript制作淘宝星级评分效果的思路
2020/06/23 Javascript
值得学习的bootstrap fileinput文件上传工具
2016/11/08 Javascript
angular或者js怎么确定选中ul中的哪几个li
2017/08/16 Javascript
使用vue的v-for生成table并给table加上序号的实例代码
2017/10/27 Javascript
Vue入门之数据绑定(小结)
2018/01/08 Javascript
Angular4.x通过路由守卫进行路由重定向实现根据条件跳转到相应的页面(推荐)
2018/05/10 Javascript
Vue脚手架的简单使用实例
2018/07/10 Javascript
@angular前端项目代码优化之构建Api Tree的方法
2018/12/24 Javascript
Vue.js实现开发购物车功能的方法详解
2019/02/22 Javascript
JavaScript实现选项卡效果的分析及步骤
2019/04/16 Javascript
基于jquery ajax的多文件上传进度条过程解析
2019/09/11 jQuery
JavaScript经典案例之简易计算器
2020/08/24 Javascript
[00:32]10月24、25日 辉夜杯外卡赛附加赛开赛!
2015/10/23 DOTA
[08:17]Ti9 现场cosplay
2019/09/10 DOTA
python使用循环实现批量创建文件夹示例
2014/03/25 Python
Python基于聚类算法实现密度聚类(DBSCAN)计算【测试可用】
2018/12/26 Python
python 定时任务去检测服务器端口是否通的实例
2019/01/26 Python
python区块及区块链的开发详解
2019/07/03 Python
Python如何实现动态数组
2019/11/02 Python
Python3.7 基于 pycryptodome 的AES加密解密、RSA加密解密、加签验签
2019/12/04 Python
python实现猜数游戏
2020/03/27 Python
python自定义函数def的应用详解
2020/06/03 Python
Python Tornado核心及相关原理详解
2020/06/24 Python
Linux内核的同步机制是什么?主要有哪几种内核锁
2016/07/11 面试题
python+selenium小米商城红米K40手机自动抢购的示例代码
2021/03/24 Python
物业管理毕业生的自我评价
2014/02/17 职场文书
《风娃娃》教学反思
2014/04/19 职场文书
超市开业庆典策划方案
2014/05/14 职场文书
车间核算员岗位职责
2014/07/01 职场文书
简单租房协议书(范本)
2014/10/13 职场文书
使用CSS定位HTML元素的实现方法
2022/07/07 HTML / CSS