Python和Ruby中each循環參考變數問題(一個隱秘BUG?)_python

來源:互聯網
上載者:User

雖然這個問題我是在 Python 裡遇到的,但是用 Ruby 解釋起來比較容易一些。在 Ruby 裡,遍曆一個數組可以有很多種方法,最常用的兩種無非是 for 和 each:

複製代碼 代碼如下:

arr = ['a', 'b', 'c']

arr.each { |e|
  puts e
}

for e in arr
  puts e
end

通常我比較喜歡後者,似乎因為寫起來比較好看,不過從效率上來說前者應該會稍微快一點,因為後者實際上是在遍曆的過程中對每個元素都調用一個 匿名函式來做的,雖然一般情況下並不明顯,不過設定上下文並調用函數確實是有開銷的,特別是在動態語言裡面(不考慮 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 的參數可以對局部變數或者全域變數之類的任何東西進行賦值,而不是通常意義上的一個 匿名函式的參數那麼簡單。由於前面的 for 語句在當前範圍建立了一個 e 作為局部變數,因此 each 就直接對這個局部變數進行賦值了,這樣,每次引用到的又變成了同一個東西,導致了一個隱秘的 Bug !

值得慶幸的是,block 的這個“特性”在 Ruby 1.9 中已經被去除了,block 的參數只能是正常參數,所以就不再存在這樣的問題了。希望 1.9 儘快普及吧!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.