Python编程中运用闭包时所需要注意的一些地方


Posted in Python onMay 02, 2015

写下这篇博客,起源于Tornado邮件群组的这个问题how to use outer variable in inner method,这里面老外的回答很有参考价值,关键点基本都说到了。我在这里用一些有趣的例子来做些解析,简要的阐述下Python的闭包规则,首先看一个经典的例子:

def foo():
 a = 1
 def bar():
  a = a + 1
  # print a + 1
  # b = a + 1
  # a = 1
  print id(a)
 
 bar()
 print a, id(a)

在Python2.x上运行这个函数会报UnboundLocalError: local variable 'a' referenced before assignment即本地变量在引用前未定义,如何来理解这个错误呢?PEP 227里面介绍到,Python解析器在搜索一个变量的定义时是根据如下三级规则来查找的:

    The Python 2.0 definition specifies exactly three namespaces to check for each name — the local namespace, the global namespace, and the builtin namespace.

这里的local实际上可能还有多级,上面的代码就是一个例子,下面通过对代码做些简单的修改来一步步理解这里面的规律:

  •     如果将a = a + 1这句换成print a + 1或者b = a + 1,是不会有问题的,即在内部函数bar内,外部函数foo里的a实际是可见的,可以引用。
  •     将a = a + 1换成 a = 1也是没有问题的,但是如果你将两处出现的a的id打印出来你会发现,其实这两个a不是一回事,在内部函数bar里面,本地的a = 1定义了在bar函数范围内的新的一个局部变量,因为名字和外部函数foo里面的变量a名字相同,导致外部函数foo里的a在内部函数bar里实际已不可见。
  •     再来说a = a + 1出错是怎么回事,首先a = xxx这种形式,Python解析器认为要在内部函数bar内创建一个新的局部变量a,同时外部函数foo里的a在bar里已不可见,而解析器对接下来对右边的a + 1的解析就是用本地的变量a加1,而这时左边的a即本地的变量a还没有创建(等右边赋值呢),因此就这就产生了一个是鸡生蛋还是蛋生鸡的问题,导致了上面说的UnboundLocalError的错误。

要解决这个问题,在Python2.x里主要有两个方案:

    用别名替代比如b = a + 1,内部函数bar内只引用外部函数foo里的a。
    将foo里的a设成一个容器,如list

  

def foo():
  a = [1, ]
  def bar():
   a[0] = a[0] + 1
  
  bar()
  print a[0]

当然这有些时候还是很不方便,因此在Python3.x中引入了一个nonloacal的关键字来解决这个问题,只要在a = a + 1前加一句nonloacal a即可,即显式的指定a不是内部函数bar内的本地变量,这样就可以在bar内正常的使用和再赋值外部函数foo内的变量a了。

在搜索Python闭包相关的材料中,我在StackOverflow上发现一个有趣的有关Python闭包的问题,有兴趣的可以思考思考做做看,结果应该是什么?你预期的结果是什么,若不一致,如果要得到你预期的结果应该怎么改?

flist = []
 
for i in xrange(3):
 def func(x): return x * i
 flist.append(func)
 
for f in flist:
 print f(2)
Python 相关文章推荐
Python挑选文件夹里宽大于300图片的方法
Mar 05 Python
详解Python中time()方法的使用的教程
May 22 Python
快速解决pandas.read_csv()乱码的问题
Jun 15 Python
如何在python字符串中输入纯粹的{}
Aug 22 Python
python如何获取当前文件夹下所有文件名详解
Jan 25 Python
将string类型的数据类型转换为spark rdd时报错的解决方法
Feb 18 Python
python Django里CSRF 对应策略详解
Aug 05 Python
Python 获取项目根路径的代码
Sep 27 Python
浅谈Pycharm最有必要改的几个默认设置项
Feb 14 Python
Python实现获取当前目录下文件名代码详解
Mar 10 Python
Django-simple-captcha验证码包使用方法详解
Nov 28 Python
Python+Matplotlib+LaTeX玩转数学公式
Feb 24 Python
按日期打印Python的Tornado框架中的日志的方法
May 02 #Python
详细解读Python的web.py框架下的application.py模块
May 02 #Python
使用Python的web.py框架实现类似Django的ORM查询的教程
May 02 #Python
在ironpython中利用装饰器执行SQL操作的例子
May 02 #Python
用Python编写简单的定时器的方法
May 02 #Python
用Python程序抓取网页的HTML信息的一个小实例
May 02 #Python
在Mac OS上部署Nginx和FastCGI以及Flask框架的教程
May 02 #Python
You might like
PHP新手上路(十一)
2006/10/09 PHP
PHP与MySQL开发中页面出现乱码的一种解决方法
2007/07/29 PHP
php设计模式 FlyWeight (享元模式)
2011/06/26 PHP
基于PHP选项与信息函数的使用详解
2013/05/10 PHP
神盾加密解密教程(三)PHP 神盾解密工具
2014/06/08 PHP
PHP代码实现爬虫记录――超管用
2015/07/31 PHP
PHP 的比较运算与逻辑运算详解
2016/05/12 PHP
php使用FFmpeg接口获取视频的播放时长、码率、缩略图以及创建时间
2016/11/07 PHP
thinkPHP中钩子的使用方法实例分析
2017/11/16 PHP
解决在laravel中leftjoin带条件查询没有返回右表为NULL的问题
2019/10/15 PHP
PHP大文件分割分片上传实现代码
2020/12/09 PHP
基于jquery实现一张图片点击鼠标放大再点缩小
2013/09/29 Javascript
框架页面高度自动刷新的Javascript脚本
2013/11/01 Javascript
jQuery实现鼠标划过修改样式的方法
2015/04/14 Javascript
JavaScript获取两个数组交集的方法
2015/06/09 Javascript
nodejs创建web服务器之hello world程序
2015/08/20 NodeJs
关于JSON.parse(),JSON.stringify(),jQuery.parseJSON()的用法
2016/06/30 Javascript
常用的js方法合集
2017/03/10 Javascript
javascript cookie的基本操作(添加和删除)
2017/07/24 Javascript
纯js实现画一棵树的示例
2017/09/05 Javascript
JS实现json对象数组按对象属性排序操作示例
2018/05/18 Javascript
关于Vue源码vm.$watch()内部原理详解
2019/04/26 Javascript
微信小程序页面传多个参数跳转页面的实现方法
2019/05/17 Javascript
vue实现Excel文件的上传与下载功能的两种方式
2019/06/28 Javascript
vue select 获取value和lable操作
2020/08/28 Javascript
[41:54]2018DOTA2亚洲邀请赛 4.1 小组赛A组加赛 TNC vs Liquid
2018/04/03 DOTA
Python编程之字符串模板(Template)用法实例分析
2017/07/22 Python
python中ASCII码和字符的转换方法
2018/07/09 Python
python安装requests库的实例代码
2019/06/25 Python
Python文件操作中进行字符串替换的方法(保存到新文件/当前文件)
2019/06/28 Python
关于tensorflow的几种参数初始化方法小结
2020/01/04 Python
利用CSS3实现圆角的outline效果的教程
2015/06/05 HTML / CSS
c++工程师面试问题
2013/08/04 面试题
员工团队活动方案
2014/08/28 职场文书
致我们终将逝去的青春观后感
2015/06/10 职场文书
热爱劳动主题班会
2015/08/14 职场文书