Python和Ruby中each循环引用变量问题(一个隐秘BUG?)


Posted in Python onJune 04, 2014

虽然这个问题我是在 Python 里遇到的,但是用 Ruby 解释起来比较容易一些。在 Ruby 里,遍历一个数组可以有很多种方法,最常用的两种无非是 for 和 each:

arr = ['a', 'b', 'c']arr.each { |e|
  puts e
}
for e in arr
  puts e
end

通常我比较喜欢后者,似乎因为写起来比较好看,不过从效率上来说前者应该会稍微快一点,因为后者实际上是在遍历的过程中对每个元素都调用一个 lambda 函数来做的,虽然一般情况下并不明显,不过设置上下文并调用函数确实是有开销的,特别是在动态语言里面(不考虑 JIT 内联优化的话)。不过这次的问题并不是性能。然而确实跟“ each 对每个元素都会新建一个 scope 而 for 则不是”有关。

看下面一段代码:

arr = ['a', 'b', 'c']
h1 = Hash.new
h2 = Hash.newarr.each { |e|
  h1[e] = lambda { e+'!'}
}
for e in arr
  h2[e] = lambda { e+'!' }
end
h1['a'].call # => ?
h2['a'].call # => ?

两个 call 分别会得到什么?应该已经猜到了吧?分别是 'a!' 和 'c!' ,后者之所以是 'c!' 是因为 for 并没有在循环的每一步都重新创建一个 scope ,因此三个 lambda 的 closure 引用到了同一个变量,而这个变量在最后一次被赋值为 'c' ,所以导致了这样的后果。

问题其实出自我在用 Python 写的一个小程序中的一段,代码类似于这样:

for prop in public_props:
    setattr(proxy, 'get_%s'%prop, lambda: self.get_prop(prop))

其中 proxy 是我提供的一个代理对象,将 self 的一些公开的属性给暴露出去,因为要限制对非 public 的属性的访问,我并不想在这个 proxy 中存放任何到 self 的引用,否则在没有访问权限限制的 Python 里通过类似 proxy._orig_self.some_private_prop 的方式来访问是轻而易举的。所以最后选择了上面那样的做法。

不幸的是,由于像刚才所说的那样,for 并没有每次都单独创建 scope ,因此 closure 全部引用到了同一个变量上,导致所有的属性值取出来都是最后一个属性了。看到这样诡异的 bug ,如果是在 C/C++ 里面,我肯定要怀疑是内存或者指针的问题了。不过想了半天才终于恍然大悟!不过 Python 里面没有 Ruby 那么方便的 each 可以用,lambda 用起来也很鸡肋,所以最后通过定义一个局部的函数来解决了:

def proxy_prop(name):
    setattr(proxy, 'get_%s'%prop, lambda: self.get_prop(name)
for prop in public_props:
    proxy_prop(prop)

最后,还要多嘴一句,对于之前 Ruby 那个例子,如果把 each 和 for 的执行顺序颠倒过来,会得到不同的结果:
arr = ['a', 'b', 'c']
h1 = Hash.new
h2 = Hash.newfor e in arr
  h2[e] = lambda { e+'!' }
end
arr.each { |e|
  h1[e] = lambda { e+'!'}
}
h1['a'].call # => 'c!'
h2['a'].call # => 'c!'

现在两个都是 'c!' 了!这是因为 Ruby 1.8 的实现里面 block 的参数可以对局部变量或者全局变量之类的任何东西进行赋值,而不是通常意义上的一个 lambda 函数的参数那么简单。由于前面的 for 语句在当前作用域创建了一个 e 作为局部变量,因此 each 就直接对这个局部变量进行赋值了,这样,每次引用到的又变成了同一个东西,导致了一个隐秘的 Bug !

值得庆幸的是,block 的这个“特性”在 Ruby 1.9 中已经被去除了,block 的参数只能是正常参数,所以就不再存在这样的问题了。希望 1.9 尽快普及吧!

Python 相关文章推荐
Python实现微信公众平台自定义菜单实例
Mar 20 Python
Python函数可变参数定义及其参数传递方式实例详解
May 25 Python
浅谈Python中函数的参数传递
Jun 21 Python
Python 解决中文写入Excel时抛异常的问题
May 03 Python
python实现自主查询实时天气
Jun 22 Python
python批量赋值操作实例
Oct 22 Python
将pip源更换到国内镜像的详细步骤
Apr 07 Python
django中SMTP发送邮件配置详解
Jul 19 Python
python flask web服务实现更换默认端口和IP的方法
Jul 26 Python
Python3 requests文件下载 期间显示文件信息和下载进度代码实例
Aug 16 Python
python3的pip路径在哪
Jun 23 Python
Python基础之数据类型知识汇总
May 18 Python
python控制台英汉汉英电子词典
Apr 23 #Python
测试、预发布后用python检测网页是否有日常链接
Jun 03 #Python
Python中的CURL PycURL使用例子
Jun 01 #Python
Python实现多线程下载文件的代码实例
Jun 01 #Python
python使用在线API查询IP对应的地理位置信息实例
Jun 01 #Python
pip 错误unused-command-line-argument-hard-error-in-future解决办法
Jun 01 #Python
2款Python内存检测工具介绍和使用方法
Jun 01 #Python
You might like
php生成局部唯一识别码LUID的代码
2012/10/06 PHP
JavaScript实现滚动栏效果的方法
2015/04/27 PHP
laravel Model 执行事务的实现
2019/10/10 PHP
javascript 关于# 和 void的区别分析
2009/10/26 Javascript
多浏览器兼容的获取元素和鼠标的位置的js代码
2009/12/15 Javascript
jQuery的学习步骤
2011/02/23 Javascript
JS分页效果示例
2013/10/11 Javascript
Jquery使用Firefox FireBug插件调试Ajax步骤讲解
2013/12/02 Javascript
Javascript学习笔记之函数篇(六) : 作用域与命名空间
2014/11/23 Javascript
javascript检测两个数组是否相似
2015/05/19 Javascript
jquery $.trim()去除字符串空格的实现方法【附图例】
2016/03/30 Javascript
使用jquery.form.js实现图片上传的方法
2016/05/05 Javascript
JavaScript实现in-place思想的快速排序方法
2016/08/07 Javascript
微信公众号开发 实现点击返回按钮就返回到聊天界面
2016/12/15 Javascript
一篇文章搞定JavaScript类型转换(面试常见)
2017/01/21 Javascript
js实现鼠标拖拽多选功能示例
2017/08/01 Javascript
angularjs select 赋值 ng-options配置方法
2018/02/28 Javascript
vue树形结构获取键值的方法示例
2018/06/21 Javascript
如何在js代码中消灭for循环实例详解
2018/07/29 Javascript
React 路由懒加载的几种实现方案
2018/10/23 Javascript
JavaScript学习笔记之数组基本操作示例
2019/01/09 Javascript
浅谈JS和jQuery的区别
2019/03/27 jQuery
js 实现ajax发送步骤过程详解
2019/07/25 Javascript
详解js location.href和window.open的几种用法和区别
2019/12/02 Javascript
Python中字典映射类型的学习教程
2015/08/20 Python
Python3.5内置模块之shelve模块、xml模块、configparser模块、hashlib、hmac模块用法分析
2019/04/27 Python
python 提取文件指定列的方法示例
2019/08/07 Python
Python进程间通信multiprocess代码实例
2020/03/18 Python
python 将视频 通过视频帧转换成时间实例
2020/04/23 Python
Python 字符串池化的前提
2020/07/03 Python
python pandas dataframe 去重函数的具体使用
2020/07/20 Python
解决Python3.8运行tornado项目报NotImplementedError错误
2020/09/02 Python
如何用 Python 处理不平衡数据集
2021/01/04 Python
Yahoo-PHP面试题2
2014/12/06 面试题
小学课改工作总结
2015/08/13 职场文书
初中班主任教育随笔
2015/08/15 职场文书