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数据结构之链表的实例讲解
Jul 25 Python
Python中使用haystack实现django全文检索搜索引擎功能
Aug 26 Python
python爬虫_微信公众号推送信息爬取的实例
Oct 23 Python
Python 生成 -1~1 之间的随机数矩阵方法
Aug 04 Python
零基础使用Python读写处理Excel表格的方法
May 02 Python
ZABBIX3.2使用python脚本实现监控报表的方法
Jul 02 Python
python操作gitlab API过程解析
Dec 27 Python
解决pyPdf和pyPdf2在合并pdf时出现异常的问题
Apr 03 Python
升级keras解决load_weights()中的未定义skip_mismatch关键字问题
Jun 12 Python
python的pip有什么用
Jun 17 Python
python3实现名片管理系统(控制台版)
Nov 29 Python
浅析Python模块之间的相互引用问题
Feb 26 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加MYSQL服务器
2006/10/09 PHP
Opcache导致php-fpm崩溃nginx返回502
2015/03/02 PHP
使用PHP生成二维码的方法汇总
2015/07/22 PHP
对采用动态原型方式无法展示继承机制得思考
2009/12/04 Javascript
js检测iframe是否加载完成的方法
2015/11/26 Javascript
盘点javascript 正则表达式中 中括号的【坑】
2016/03/16 Javascript
深入理解JS DOM事件机制
2016/08/06 Javascript
Bootstrap CSS布局之图像
2016/12/17 Javascript
树结构之JavaScript
2017/01/24 Javascript
vue2 如何实现div contenteditable=“true”(类似于v-model)的效果
2017/02/08 Javascript
微信小程序日历组件calendar详解及实例
2017/06/08 Javascript
初探JavaScript 面向对象(推荐)
2017/09/03 Javascript
基于Vue自定义指令实现按钮级权限控制思路详解
2018/05/23 Javascript
Vue实现左右菜单联动实现代码
2018/08/12 Javascript
Vue源码中要const _toStr = Object.prototype.toString的原因分析
2018/12/09 Javascript
基于 jQuery 实现键盘事件监听控件
2019/04/04 jQuery
Vue项目中使用jquery的简单方法
2019/05/16 jQuery
layui.use模块外部使用其内部定义的js封装函数方法
2019/09/16 Javascript
如何封装Vue Element的table表格组件
2021/02/06 Vue.js
python如何将图片转换为字符图片
2020/08/19 Python
python远程连接MySQL数据库
2019/04/19 Python
Python 类的私有属性和私有方法实例分析
2019/09/29 Python
tensorflow使用L2 regularization正则化修正overfitting过拟合方式
2020/05/22 Python
python搜索算法原理及实例讲解
2020/11/18 Python
html5简介_动力节点Java学院整理
2017/07/07 HTML / CSS
美体小铺印度官网:The Body Shop印度
2019/10/17 全球购物
网络专业学生个人的自我评价
2013/12/16 职场文书
自我评价个人范文
2013/12/16 职场文书
文明教师事迹材料
2014/01/16 职场文书
汽车机修工岗位职责
2014/03/06 职场文书
运动会广播稿200字(10篇)
2014/10/12 职场文书
护士自荐信怎么写
2015/03/06 职场文书
学籍证明模板
2015/06/18 职场文书
2016年中学端午节主题活动总结
2016/04/01 职场文书
关于Numpy之repeat、tile的用法总结
2021/06/02 Python
《LOL》“克隆大作战”久违归来 幻灵战队皮肤上线
2022/04/03 其他游戏