`

Python和Ruby中each循环引用变量问题(一个隐秘BUG?)

    博客分类:
  • ruby
阅读更多
这篇文章主要介绍了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!'

现在两个都是 'c!' 了!这是因为 Ruby 1.8 的实现里面 block 的参数可以对局部变量或者全局变量之类的任何东西进行赋值,而不是通常意义上的一个 lambda 函数的参数那么简单。由于前面的 for 语句在当前作用域创建了一个 e 作为局部变量,因此 each 就直接对这个局部变量进行赋值了,这样,每次引用到的又变成了同一个东西,导致了一个隐秘的 Bug !

 

值得庆幸的是,block 的这个“特性”在 Ruby 1.9 中已经被去除了,block 的参数只能是正常参数,所以就不再存在这样的问题了。希望 1.9 尽快普及吧

分享到:
评论

相关推荐

    Python & Ruby 学习

    标题中的“Python & Ruby 学习”表明了这个压缩包文件包含了与这两种编程语言学习相关的资源。Python 和 Ruby 都是流行的高级编程语言,广泛应用于Web开发、数据分析、自动化脚本等多个领域。以下是对这两个语言的...

    关于Python如何避免循环导入问题详解

    module_a.py中定义了一个action_a()函数,该函数引用了module_b.py中的一个attribute,如一个函数或变量 module_b.py中定义了一个action_b()函数,该函数引用了module_a.py中的一个attribute,如一个函数或变量 ...

    为什么你一定要学习Python或Ruby语言.pdf

    Python和Ruby是两种强大的、高效且易学的编程语言,它们在现代软件开发中扮演着重要的角色。本文将探讨为什么学习Python或Ruby对于程序员来说是必要的,并与一些常见的编程语言进行对比。 首先,Python和Ruby相比C/...

    Python和Ruby比较优缺点共1页.pdf.zip

    Python和Ruby都是高级编程语言,它们在Web开发、脚本编写、数据分析等领域有着广泛的...在实际工作中,了解并掌握多种语言可以提高解决问题的灵活性,而深入理解Python和Ruby的优缺点将有助于做出更适合项目的决策。

    Python语言基础:for循环语句.pptx

    在这个结构中,`变量`将依次取序列或迭代对象中的每一个元素,每次迭代时,`循环体语句块`会被执行。例如,如果我们要遍历0到9的整数,可以使用`range()`函数: ```python for i in range(10): # 这里执行的代码会...

    python和ruby,我选谁?

    - **灵活的控制结构**:Ruby中的“块”提供了一种更通用且灵活的方式来处理循环和条件语句,相比于Python的列表推导更为强大。 - **函数式编程支持**:Ruby支持更接近Lisp的函数式编程特性,增强了代码的表达力。 #...

    Python程序基础:Python中的变量.pptx

    变量名只能包括字母、数字和下划线,且第一个字符必须是字母或下划线,不能是数字。 str,_str1,str_2 2str,2_str,&123,%lsso,M.Jack,-L2 例如: 第一个单词首字母小写,之后的单词首字母大写,如myName,...

    基于Python实现全局和局部双变量Moran指数计算

    基于Python实现全局和局部双变量Moran指数计算,输入参数可直接是shapefile文件。

    python 3.7 静态变量和内部变量的bug(csdn)————程序.pdf

    在Python编程语言中,静态变量和内部变量是两个重要的概念,它们在类和函数的上下文中扮演着不同的角色。在Python 3.7版本中,虽然没有像其他面向对象语言如Java那样明确的静态变量定义语法,但可以通过类变量来模拟...

    Python语言基础:变量.pptx

    以上就是Python语言中关于变量和运算符的基础知识,这些是编写Python程序的基础,能够帮助我们进行数据的存储和计算,从而实现各种复杂的功能,包括题目中提到的计算圆形的参数。在处理圆形参数时,我们需要了解圆的...

    初中Python程序设计循环结构教学设计方案.pdf

    在这个例子中,`for`循环会依次取出这个序列中的每一个数字赋给变量`i`,并执行循环体内的语句。当`i`的值达到101时,循环结束。 2. 累加求和的应用: `sum=0`初始化了一个累加器变量`sum`,用于存储累加的和。`sum...

    用Python,Lua和Ruby语言设计游戏-Game.Programming.with.Python.Lua.And.Ruby

    Ruby的Gosu库提供了一个基础的2D游戏开发框架,支持图像、声音和输入处理。对于喜欢Ruby的开发者,它可以作为一个快速开发小型游戏的工具。 综合来看,Python、Lua和Ruby各有其在游戏开发中的优势。Python适合快速...

    python 循环批量生成变量(csdn)————程序.pdf

    这段代码会在每次循环中生成一个以`x`开头,后面跟着循环变量`i`的字符串形式的新变量,然后将其值设置为`2 * i + 3`。之后的`for`循环则打印出这些变量的值,例如`x0`到`x9`分别对应`3`到`21`。 然而,这种做法并...

    Python、Ruby游戏设计参考图书

    根据给定的信息,“Python、Ruby游戏设计参考图书”是一本详细介绍如何使用Python和Ruby这两种脚本语言进行游戏设计与开发的书籍。本书不仅涵盖了游戏设计的基本原理,还深入探讨了如何利用这两种流行的编程语言来...

    python中变量作用域及嵌套作用域.pdf

    Python 中变量作用域及嵌套作用域 Python 中的变量作用域是指变量的可见范围和生命周期。变量作用域可以分为四个级别:局部作用域(Local Scope)、外部作用域(Enclosing Scope)、全局作用域(Global Scope)和内...

    Python基础知识之Python中循环结构for循环与while循环.docx

    `range()` 是Python内置的一个函数,主要用于生成一个整数序列,这些序列通常用于循环结构中,如`for`循环。`range()`函数支持三种不同的调用方式: 1. **单参数形式**: ```python range(stop) ``` 此方式仅...

    用Python,Lua和Ruby语言设计游戏-Game.Programming.with.Python.Lua.And.Ruby.

    在游戏开发领域,Python、Lua和Ruby这三种脚本语言因其简洁、高效和易学习的特点,逐渐成为开发者们的首选工具。《用Python,Lua和Ruby语言设计游戏-Game.Programming.with.Python.Lua.And.Ruby》这本书深入探讨了...

    python设置环境变量

    使用Python设置环境变量

    Python中一个for循环循环多个变量的示例

    今天我们将深入探讨如何在一个`for`循环中同时循环多个变量。这在处理多个相关数据集时非常有用,比如当你需要同时操作一对配对的数据时。 首先,我们要介绍的关键工具是内置函数`zip()`。`zip()`函数的作用是将多...

Global site tag (gtag.js) - Google Analytics