虽然这个问题我是在 Python 里遇到的,但是用 Ruby 解释起来比较容易一些。在 Ruby 里,遍历一个数组可以有很多种方法,最常用的两种无非是 for 和 each:
arr = ['a', 'b', 'c'] arr.each { |e| puts e } for e in arr puts e end
看下面一段代码:
arr = ['a', 'b', 'c'] h1 = Hash.new h2 = Hash.new arr.each { |e| h1[e] = lambda { e+'!'} } for e in arr h2[e] = lambda { e+'!' } end h1['a'].call # => ? h2['a'].call # => ?
问题其实出自我在用 Python 写的一个小程序中的一段,代码类似于这样:
for prop in public_props: setattr(proxy, 'get_%s'%prop, lambda: self.get_prop(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)
arr = ['a', 'b', 'c'] h1 = Hash.new h2 = Hash.new for e in arr h2[e] = lambda { e+'!' } end arr.each { |e| h1[e] = lambda { e+'!'} } h1['a'].call # => 'c!' h2['a'].call # => 'c!'
值得庆幸的是,block 的这个“特性”在 Ruby 1.9 中已经被去除了,block 的参数只能是正常参数,所以就不再存在这样的问题了。希望 1.9 尽快普及吧!