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

虽然这个问题我是在 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 尽快普及吧!

(0)

相关推荐

  • Ruby对比Python的优势和劣势

    Ruby 和 Python 太相似了,取舍大部分都是个人喜好上的原因.比如我就觉得 Python 的 "There is only one way to do it." 比 Ruby 的 "There are many ways to do it." 要好,这不光是考虑团队协作的问题,更重要的是自己能很快明白自己三个月前写的没有任何注释的代码是在干什么.当然也有很多人觉得自由和灵活要比可读性来的重要,所以我说这个是个人喜好的原因. 客观上的 Ruby 比 Pytho

  • 每个程序员都应该学习使用Python或Ruby

    如果你是个学生,你应该会C,C++和Java.还会一些VB,或C#/.NET.多少你还可能开发过一些Web网页,你知道一些HTML,CSS和JavaScript知识.总体上说,我们很难发现会有学生显露出掌握超出这几种语言范围外的语言的才能.这真让人遗憾,因为还有很多种编程语言,它们能让你成为一个更好的程序员. 在这篇文章里,我将会告诉你,为什么你一定要学习Python或Ruby语言. 跟C/C++/Java相比 - Python/Ruby能让你用少的多的多的代码写出相同的程序.有人计算过,Pyt

  • 学编程选什么语言好?是PHP、Python还是Ruby?

    简单地一句话总结: 1.假如你想帮他尽快找个活儿,赚到钱,推荐PHP. 2.假如你想让他成为一个高效工程师,推荐 Python. 3.假如你想让他爱上他的工作,推荐 Ruby. 语言的选择: 编程语言非常重要,不要认为他们都图灵等价,用起来都一样.实际上,好的语言,带给你的东西是超乎想像的. 下面是一些看法: 1.程序员的时间远比机器的时间宝贵:选择开发效率最高的语言吧,不要过于在乎运行性能,如果你开发不出东西,那么跑得多快也没用. 2.优雅的抽象胜于简单的堆砌: 这意味着你的代码是最简洁而又充

  • python和ruby,我选谁?

    最近在考虑学习一门后端语言,在ruby和python直接犹豫,然后自己做了一些对比,希望能帮到有同样问题的你. 一.异同对比选择 1.Python和ruby的相同点: •都强调语法简单,都具有更一般的表达方式.python是缩进,ruby是类basic的表达.都大量减少了符号. •都是动态数据类型.都是有丰富的数据结构. •都具有C语言扩展能力,都具有可移植性,比perl的可移植性更好.也都可以作为嵌入语言. •都是面向对象的语言,都可以作为大项目的开发工具. •都有丰富的库支持. •也有最宽松

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

    虽然这个问题我是在 Python 里遇到的,但是用 Ruby 解释起来比较容易一些.在 Ruby 里,遍历一个数组可以有很多种方法,最常用的两种无非是 for 和 each: 复制代码 代码如下: arr = ['a', 'b', 'c'] arr.each { |e|  puts e} for e in arr  puts eend 通常我比较喜欢后者,似乎因为写起来比较好看,不过从效率上来说前者应该会稍微快一点,因为后者实际上是在遍历的过程中对每个元素都调用一个 lambda 函数来做的,虽

  • 详解Ruby中的循环语句的用法

    Ruby 中的循环用于执行相同的代码块若干次.本章节将详细介绍 Ruby 支持的所有循环语句. Ruby while 语句 语法 while conditional [do] code end 当 conditional 为真时,执行 code.while 循环的 conditional 通过保留字 do.一个换行符.反斜线 \ 或一个分号 ; ,来与 code 分离开. 实例 #!/usr/bin/ruby $i = 0 $num = 5 while $i < $num do puts("

  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题. // // main.cpp // test // // Created by 杜国超 on 17/9/9. // Copyright © 2017年 杜国超. All rights reserved. // #include <iostream> #include <memory> #include <vector>

  • Ruby中的循环语句的用法教程

    Ruby中的循环用于执行相同的代码块指定的次数.本章将详细介绍Ruby支持的循环语句. Ruby while 语句: 语法: while conditional [do]    code end 执行代码当条件为true时.while循环的条件是代码中的保留字,换行,反斜杠(\)或一个分号隔开. 实例: #!/usr/bin/ruby $i = 0 $num = 5 while $i < $num do puts("Inside the loop i = #$i" ) $i +=

  • 实例讲解Ruby中的五种变量

    Ruby 全局变量 全局变量以 $ 开头.未初始化的全局变量的值为 nil,在使用 -w 选项后,会产生警告. 给全局变量赋值会改变全局状态,所以不建议使用全局变量. 下面的实例显示了全局变量的用法. #!/usr/bin/ruby $global_variable = 10 class Class1 def print_global puts "Global variable in Class1 is #$global_variable" end end class Class2 d

  • Objective-C中block循环引用问题详解

    目标:block执行过程中,self不会释放:执行完可以释放. 最初 block中直接使用self会强引用. self.myBlock = ^() { [self doSomething]; }; 或者使用了对象的属性 self.myBlock = ^() { NSString *str = _str; NSString *str2 = self.str; }; 在这样的情况下,self强引用block,block也持有该对象,导致循环引用. 要注意的是,只有在self强引用block的时候才会

  • 关于NodeJS中的循环引用详解

    最近在用node的时候排查一个问题排查了半天,最终发现是循环引用导致的问题,故在此记录一下. 场景复现 出现问题场景比较简单,一共四个类: parent.ts child.ts child_2.ts util.ts export abstract class Parent { abstract hello(): string; } import {Parent} from "./parent"; export class Child extends Parent { hello():

  • ruby中的循环语句总结

    while(当-) 循环 while 条件 语句1; 语句2 ; 语句- end 单行 while 循环 ( 语句1; 语句2 ; 语句- ) while 条件 until(直到-) 循环 until 条件 = while not (条件) for-in 循环 for 变量 in 对象 语句1; 语句2 ; 语句- end break 跳出当层循环 next 忽略本次循环的剩余部分,开始下一次的循环 redo 重新开始循环,还是从这一次开始 retry 重头开始这个循环体 times 3.tim

  • python如何在循环引用中管理内存

    python中通过引用计数来回收垃圾对象,在某些环形数据结构(树,图--),存在对象间的循环引用,比如树的父节点引用子节点,子节点同时引用父节点,此时通过del掉引用父子节点,两个对象不能被立即释放 需求: 如何解决此类的内存管理问题? 如何查询一个对象的引用计数? import sys sys.getrefcount(obj) # 查询引用计数必多 1 ,因为object也引用 查询对象 如何解决内存管理问题? 通过weakref,进行弱引用,当del时候,不再引用,在引用方添加weakref

  • Ruby教程之注释、变量声明以及数组操作

    前两天在"博客园"上看了一篇文章"PHP基础教程",介绍PHP的,感觉挺好.D瓜哥在学Ruby,正好也写一篇"Ruby入门教程".需要说明一下,这篇文章适合有编程基础的,但是没接触过Ruby的新手.而且,这篇文章侧重入门,老鸟可以直接飞过.(如果能帮忙检查一下是否有描述不当,甚至错误的地方,也欢迎来踩两脚.D瓜哥感激不尽.) Ruby环境搭建 在Windows下,搭建Ruby环境,比较简单的方法是在"RubyInstaller"

随机推荐