標題看起來很虎人,其實不敢稱為分析。自己這方面仍有欠缺,以前也許還行,現在專門研究語言的時間和精力沒那麼多了。有解釋的不對的地方歡迎各位來板磚,別誤導了大眾。
還是直接說這次的問題,今天@neiddy(javaeye)跟我說起閉包的問題,看那幾個例子好有意思,想搞懂的衝動。
看兩段程式碼:
>>> def foo(): a = 1 def bar(): a = a +1 return a return bar() >>> foo() Traceback (most recent call last): File "<pyshell#73>", line 1, in <module> foo() File "<pyshell#72>", line 6, in foo return bar() File "<pyshell#72>", line 4, in bar a = a +1 UnboundLocalError: local variable 'a' referenced before assignment
>>> def foo(): a = [1] def bar(): a[0] = a[0] + 1 return a[0] return bar() >>> foo()2
透過閉包體驗函數式編程,還是不錯的感覺。原文下面少了括號,不然回傳函數本身就沒意思了。再說這個神奇的現象,文中只是說改成容器就ok了,為什麼呢?
Google了一些記憶體管理記憶體分配的東西,都沒有找到出路,走投無路只能投奔源碼了。從來沒看過直接看效率太低,幸好有人總結過了,推薦一下」雨痕 Q.yuhen」的《深入Python編程》,實屬低調的大神。
直接閉包部分的程式碼分析雨痕在閉包的一節有講到,但是沒有專門說這個奇怪的問題的機制。
重複的看書吧,參考了書中的’參數’和’閉包’兩節,下面說說根據他的講解和附上的源碼,談談我的理解。
雨痕在章節的最後提到」CPython實現閉包的原理並不復雜,說白了就是將所引用的外層物件附加到每次都重新創建的內層函數物件身上(func_closure)。」
這句話是對前面的概括,同時也包含了重要的訊息,就是內部函數對應的物件存取外層函數中的變數其實是透過將外層的變數引用到記憶體物件的堆疊中來存取的, C語言的程式碼中時會按值傳遞的。
例如第一段程式碼中的a,其實是引用了1過來,本身的co_nlocals是1,也就是一個局部變數是等號前面的a(這樣說不太對,只是希望幫忙理解這個問題)。既然是局部變數a,a = a +1必然是要拋出UnboundLocalError的。
而對於第二個問題,雖然存在一樣的情況,但是即便按值傳遞,數組中每個位置的指針指向的具體是不變的,還是會修改指定位置的值,因此如果是容器型對象就是可行的。
痛恨自己的就是這個地方總覺得自己說不清楚,其實就是剛學c語言的時候常玩的指針類遊戲,雖然說的很爛,但希望指到要害了。接下來就很簡單了,按照這個想法來驗證一段程式碼,也就是如果只是輸出這個值,不設定局部變數的話那麼應該是可以運行的。
>>> def foo(): a = 1 def bar(): return a return bar() >>> foo() 1
事情果然跟預想的一樣發生了。再細細的體會,好好看看雨痕帶著分析的程式碼。了解機制走的更遠。 Python越來越有意思了,用python的想法寫程式碼,益處良多啊。
By the way,順帶
>>> flist = [] >>> for i in range(3): def foo(x): print x + i flist.append(foo) >>> for f in flist: f(2) 4 4 4
文中提到這段程式碼是因為i不被銷毀導致的,今天搜尋記憶體分配的時候也看到這個東西。程式設計的時候應該盡量使用list comprehension減少for和while,一是函數化程式設計簡潔明了,另一方面是效能提升和節省一個計數器。