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正则表达式匹配HTML页面编码
Apr 08 Python
200 行python 代码实现 2048 游戏
Jan 12 Python
numpy 对矩阵中Nan的处理:采用平均值的方法
Oct 30 Python
Python编程中类与类的关系详解
Aug 08 Python
Pytorch中的variable, tensor与numpy相互转化的方法
Oct 10 Python
tensorflow没有output结点,存储成pb文件的例子
Jan 04 Python
python 按钮点击关闭窗口的实现
Mar 04 Python
使用OpenCV获取图片连通域数量,并用不同颜色标记函
Jun 04 Python
PyCharm MySQL可视化Database配置过程图解
Jun 09 Python
Python字符串三种格式化输出
Sep 17 Python
Python Django获取URL中的数据详解
Nov 01 Python
python lambda 表达式形式分析
Apr 03 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 print EOF实现方法
2009/05/21 PHP
PHP array_flip() 删除重复数组元素专用函数
2010/05/16 PHP
PHP简单实现模拟登陆功能示例
2017/09/15 PHP
网站导致浏览器崩溃的原因总结(多款浏览器) 推荐
2010/04/15 Javascript
JS打开图片另存为对话框实现代码
2012/12/26 Javascript
JS下拉缓冲菜单示例代码
2013/08/30 Javascript
JavaScript实现数字数组正序排列的方法
2015/04/06 Javascript
一个简单不报错的summernote 图片上传案例
2016/07/11 Javascript
js弹出窗口简单实现代码
2017/03/22 Javascript
Mint UI 基于 Vue.js 移动端组件库
2017/11/07 Javascript
vue自定义指令directive实例详解
2018/01/17 Javascript
解决Linux无法正常安装与卸载Node.js的方法
2018/01/19 Javascript
jQuery实现动态加载select下拉列表项功能示例
2018/05/31 jQuery
Vue引用Swiper4插件无法重写分页器样式的解决方法
2018/09/27 Javascript
vue v-for 使用问题整理小结
2019/08/04 Javascript
jquery实现商品sku多属性选择功能(商品详情页)
2019/12/20 jQuery
jQuery实现B2B网站后台管理系统侧导航
2020/07/08 jQuery
跟老齐学Python之编写类之四再论继承
2014/10/11 Python
研究Python的ORM框架中的SQLAlchemy库的映射关系
2015/04/25 Python
Python 专题一 函数的基础知识
2017/03/16 Python
pycharm打开命令行或Terminal的方法
2019/01/16 Python
利用python-docx模块写批量生日邀请函
2019/08/26 Python
python GUI库图形界面开发之PyQt5窗口类QMainWindow详细使用方法
2020/02/26 Python
打印tensorflow恢复模型中所有变量与操作节点方式
2020/05/26 Python
python pygame 愤怒的小鸟游戏示例代码
2021/02/25 Python
CSS3实现可关闭的下拉手风琴菜单效果
2015/08/31 HTML / CSS
卡西欧B级产品官方网站:Casio Outlet
2018/05/22 全球购物
Levi’s西班牙官方网站:李维斯,著名的牛仔裤品牌
2020/08/20 全球购物
在浏览器端如何得到服务器端响应的XML数据
2012/11/24 面试题
工商管理专业实习生自我鉴定
2013/09/29 职场文书
安全生产检查通报
2014/01/29 职场文书
户外拓展活动方案
2014/02/11 职场文书
挂科检讨书范文
2014/02/20 职场文书
十八届三中全会宣传方案
2014/02/21 职场文书
青春励志演讲稿
2014/04/29 职场文书
汽车销售合同文本
2019/08/08 职场文书