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 的执行顺序颠倒过来,会得到不同的结果:
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 尽快普及吧!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











PS "로드"문제는 자원 액세스 또는 처리 문제로 인한 것입니다. 하드 디스크 판독 속도는 느리거나 나쁘다 : CrystalDiskinfo를 사용하여 하드 디스크 건강을 확인하고 문제가있는 하드 디스크를 교체하십시오. 불충분 한 메모리 : 고해상도 이미지 및 복잡한 레이어 처리에 대한 PS의 요구를 충족시키기 위해 메모리 업그레이드 메모리. 그래픽 카드 드라이버는 구식 또는 손상됩니다. 운전자를 업데이트하여 PS와 그래픽 카드 간의 통신을 최적화하십시오. 파일 경로는 너무 길거나 파일 이름에는 특수 문자가 있습니다. 짧은 경로를 사용하고 특수 문자를 피하십시오. PS 자체 문제 : PS 설치 프로그램을 다시 설치하거나 수리하십시오.

느린 Photoshop 스타트 업 문제를 해결하려면 다음을 포함한 다중 프론트 접근 방식이 필요합니다. 하드웨어 업그레이드 (메모리, 솔리드 스테이트 드라이브, CPU); 구식 또는 양립 할 수없는 플러그인 제거; 정기적으로 시스템 쓰레기 및 과도한 배경 프로그램 청소; 주의를 기울여 관련없는 프로그램 폐쇄; 시작하는 동안 많은 파일을 열지 않도록합니다.

부팅 할 때 "로드"에 PS가 붙어있는 여러 가지 이유로 인해 발생할 수 있습니다. 손상되거나 충돌하는 플러그인을 비활성화합니다. 손상된 구성 파일을 삭제하거나 바꾸십시오. 불충분 한 메모리를 피하기 위해 불필요한 프로그램을 닫거나 메모리를 업그레이드하십시오. 하드 드라이브 독서 속도를 높이기 위해 솔리드 스테이트 드라이브로 업그레이드하십시오. 손상된 시스템 파일 또는 설치 패키지 문제를 복구하기 위해 PS를 다시 설치합니다. 시작 오류 로그 분석의 시작 과정에서 오류 정보를 봅니다.

<p> 다음 페이지 기능은 HTML을 통해 만들 수 있습니다. 단계에는 컨테이너 요소 만들기, 컨텐츠 분할, 탐색 링크 추가, 다른 페이지 숨기기 및 스크립트 추가가 포함됩니다. 이 기능을 통해 사용자는 세분화 된 컨텐츠를 탐색하여 한 번에 한 페이지 씩 만 표시 할 수 있으며 많은 양의 데이터 또는 콘텐츠를 표시하는 데 적합합니다. </p>

"로드"는 PS에서 파일을 열 때 말더듬이 발생합니다. 그 이유에는 너무 크거나 손상된 파일, 메모리 불충분, 하드 디스크 속도가 느리게, 그래픽 카드 드라이버 문제, PS 버전 또는 플러그인 충돌이 포함될 수 있습니다. 솔루션은 다음과 같습니다. 파일 크기 및 무결성 확인, 메모리 증가, 하드 디스크 업그레이드, 그래픽 카드 드라이버 업데이트, 의심스러운 플러그인 제거 또는 비활성화 및 PS를 다시 설치하십시오. 이 문제는 PS 성능 설정을 점차적으로 확인하고 잘 활용하고 우수한 파일 관리 습관을 개발함으로써 효과적으로 해결할 수 있습니다.

PS 로딩이 느린 이유는 하드웨어 (CPU, 메모리, 하드 디스크, 그래픽 카드) 및 소프트웨어 (시스템, 백그라운드 프로그램)의 결합 된 영향 때문입니다. 솔루션에는 하드웨어 업그레이드 (특히 솔리드 스테이트 드라이브 교체), 소프트웨어 최적화 (시스템 쓰레기 청소, 드라이버 업데이트, PS 설정 확인) 및 PS 파일 처리가 포함됩니다. 정기적 인 컴퓨터 유지 보수는 또한 PS 달리기 속도를 향상시키는 데 도움이 될 수 있습니다.

PS 카드가 "로드"되어 있습니까? 솔루션에는 컴퓨터 구성 (메모리, 하드 디스크, 프로세서) 확인, 하드 디스크 조각 청소, 그래픽 카드 드라이버 업데이트, PS 설정 조정, PS 재설치 및 우수한 프로그래밍 습관 개발이 포함됩니다.

PS에서 PDF를 배치로 내보내는 세 가지 방법이 있습니다 : PS 동작 기능 사용 : 파일을 기록하고 열기 및 PDF 작업을 내보내고, 루프에서 동작을 실행합니다. 타사 소프트웨어의 도움으로 : 파일 관리 소프트웨어 또는 자동화 도구를 사용하여 입력 및 출력 폴더를 지정하고 파일 이름 형식을 설정하십시오. 스크립트 사용 : 스크립트를 작성하여 배치 내보내기 로직을 사용자 정의하지만 프로그래밍 지식이 필요합니다.
