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中优化NumPy包使用性能的教程
Apr 23 Python
python 默认参数问题的陷阱
Feb 29 Python
Python中基础的socket编程实战攻略
Jun 01 Python
Python使用min、max函数查找二维数据矩阵中最小、最大值的方法
May 15 Python
Python中按值来获取指定的键
Mar 04 Python
python文件写入write()的操作
May 14 Python
python调用摄像头拍摄数据集
Jun 01 Python
简单了解python反射机制的一些知识
Jul 13 Python
django实现用户注册实例讲解
Oct 30 Python
Python StringIO及BytesIO包使用方法解析
Jun 15 Python
keras分类之二分类实例(Cat and dog)
Jul 09 Python
Python使用random模块实现掷骰子游戏的示例代码
Apr 29 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
Laravel统一错误处理为JSON的方法介绍
2020/10/18 PHP
基于JQuery的asp.net树实现代码
2010/11/30 Javascript
JQuery动态给table添加、删除行 改进版
2011/01/19 Javascript
基于mootools插件实现遮罩层新手引导
2012/05/24 Javascript
让复选框只能选择一项的方法
2013/10/08 Javascript
用jquery中插件dialog实现弹框效果实例代码
2013/11/15 Javascript
jquery实现当滑动到一定位置时固定效果
2014/06/17 Javascript
js支持键盘控制的左右切换立体式图片轮播效果代码分享
2015/08/26 Javascript
JS简单获取及显示当前时间的方法
2016/08/03 Javascript
微信小程序 支付功能实现PHP实例详解
2017/05/12 Javascript
Vue ElementUI之Form表单验证遇到的问题
2017/08/21 Javascript
EL表达式截取字符串的函数说明
2017/09/22 Javascript
推荐10款扩展Web表单的JS插件
2017/12/25 Javascript
js+canvas实现滑动拼图验证码功能
2018/03/26 Javascript
JS高级技巧(简洁版)
2018/07/29 Javascript
搭建基于express框架运行环境的方法步骤
2018/11/15 Javascript
Node.js 深度调试方法解析
2020/07/28 Javascript
[01:52]2014DOTA2西雅图邀请赛 V社开大会你不知道的小秘密
2014/07/08 DOTA
Python中取整的几种方法小结
2017/01/06 Python
python编写简易聊天室实现局域网内聊天功能
2018/07/28 Python
Python redis操作实例分析【连接、管道、发布和订阅等】
2019/05/16 Python
django重新生成数据库中的某张表方法
2019/08/28 Python
详解pycharm连接不上mysql数据库的解决办法
2020/01/10 Python
Python爬虫+tkinter界面实现历史天气查询的思路详解
2021/02/22 Python
物流专员岗位职责
2014/02/17 职场文书
勤俭节约倡议书
2014/04/14 职场文书
《春到梅花山》教学反思
2014/04/16 职场文书
毕业设计说明书
2014/05/07 职场文书
国际贸易专业求职信
2014/06/04 职场文书
大专学生求职信
2014/07/04 职场文书
2014年综合治理工作总结
2014/11/20 职场文书
幼儿园秋季开学通知
2015/07/16 职场文书
高中语文教学反思范文
2016/02/16 职场文书
优秀家长事迹材料(2016推荐版)
2016/02/29 职场文书
MySQL命令无法输入中文问题的解决方式
2021/08/30 MySQL
CentOS7设置ssh服务以及端口修改方式
2022/12/24 Servers