这篇文章主要介绍了Python和Ruby中each循环引用变量问题,类似PHP的foreach中使用引用变量的问题,需要的朋友可以参考下
虽然这个问题我是在 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.new
arr.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.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!'
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!'
现在两个都是 'c!' 了!这是因为 Ruby 1.8 的实现里面 block 的参数可以对局部变量或者全局变量之类的任何东西进行赋值,而不是通常意义上的一个 lambda 函数的参数那么简单。由于前面的 for 语句在当前作用域创建了一个 e 作为局部变量,因此 each 就直接对这个局部变量进行赋值了,这样,每次引用到的又变成了同一个东西,导致了一个隐秘的 Bug !
值得庆幸的是,block 的这个“特性”在 Ruby 1.9 中已经被去除了,block 的参数只能是正常参数,所以就不再存在这样的问题了。希望 1.9 尽快普及吧
相关推荐
还有诸如Gosu这样的Ruby库,它提供了一个简单的2D游戏开发接口,类似于Pygame,但具有Ruby的特性。Ruby的元编程能力也让开发者能灵活地调整游戏行为,以适应不断变化的需求。 结合这三种语言,我们可以构建出复杂的...
在Python中,for循环的嵌套是完全允许的,可以将一个for循环放在另一个for循环内部,形成多层循环结构,以此实现更复杂的逻辑。 嵌套for循环的基本语法如下: ```python for 外层取值 in 外层序列或迭代对象: for...
module_a.py中定义了一个action_a()函数,该函数引用了module_b.py中的一个attribute,如一个函数或变量 module_b.py中定义了一个action_b()函数,该函数引用了module_a.py中的一个attribute,如一个函数或变量 ...
Python和Ruby是两种强大的、高效且易学的编程语言,它们在现代软件开发中扮演着重要的角色。本文将探讨为什么学习Python或Ruby对于程序员来说是必要的,并与一些常见的编程语言进行对比。 首先,Python和Ruby相比C/...
而如果你更看重代码的表达力和Web开发的便捷性,Ruby和Ruby on Rails可以提供一个优雅的解决方案。 总的来说,Python和Ruby都是优秀的编程语言,它们在不同的应用场景下各有所长。理解它们的优缺点有助于我们根据...
在这个结构中,`变量`将依次取序列或迭代对象中的每一个元素,每次迭代时,`循环体语句块`会被执行。例如,如果我们要遍历0到9的整数,可以使用`range()`函数: ```python for i in range(10): # 这里执行的代码会...
基于Python实现全局和局部双变量Moran指数计算,输入参数可直接是shapefile文件。
这篇书籍《用Python, Lua和Ruby语言设计游戏》旨在为初学者提供一个了解和使用这些语言进行游戏开发的入门指南。以下是关于这三个语言在游戏设计中的核心知识点: 1. Python: - **易学性**:Python以其简洁的语法...
变量名只能包括字母、数字和下划线,且第一个字符必须是字母或下划线,不能是数字。 str,_str1,str_2 2str,2_str,&123,%lsso,M.Jack,-L2 例如: 第一个单词首字母小写,之后的单词首字母大写,如myName,...
在Python编程语言中,静态变量和内部变量是两个重要的概念,它们在类和函数的上下文中扮演着不同的角色。在Python 3.7版本中,虽然没有像其他面向对象语言如Java那样明确的静态变量定义语法,但可以通过类变量来模拟...
以上就是Python语言中关于变量和运算符的基础知识,这些是编写Python程序的基础,能够帮助我们进行数据的存储和计算,从而实现各种复杂的功能,包括题目中提到的计算圆形的参数。在处理圆形参数时,我们需要了解圆的...
在这个例子中,`for`循环会依次取出这个序列中的每一个数字赋给变量`i`,并执行循环体内的语句。当`i`的值达到101时,循环结束。 2. 累加求和的应用: `sum=0`初始化了一个累加器变量`sum`,用于存储累加的和。`sum...
Python 中变量作用域及嵌套作用域 Python 中的变量作用域是指变量的可见范围和生命周期。变量作用域可以分为四个级别:局部作用域(Local Scope)、外部作用域(Enclosing Scope)、全局作用域(Global Scope)和内...
`range()` 是Python内置的一个函数,主要用于生成一个整数序列,这些序列通常用于循环结构中,如`for`循环。`range()`函数支持三种不同的调用方式: 1. **单参数形式**: ```python range(stop) ``` 此方式仅...
在游戏开发领域,Python、Lua和Ruby这三种脚本语言因其简洁、高效和易学习的特点,逐渐成为开发者们的首选工具。《用Python,Lua和Ruby语言设计游戏-Game.Programming.with.Python.Lua.And.Ruby》这本书深入探讨了...
使用Python设置环境变量
循环结构作为Python程序设计的三大基本结构之一,有助于学生更好的解决生活中的实际问题,通过这节课的学习,学生会对循环结构有个更深入的了解,并为三种结构的综合学习奠定基础,所以是本单元的重点之一。...