標題看起來很虎人,其實不敢稱為分析。自己這方面仍有欠缺,以前也許還行,現在專門研究語言的時間和精力沒那麼多了。有解釋的不對的地方歡迎各位來板磚,別誤導了福士。
還是直接說這次的問題,今天@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 +1UnboundLocalError: 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) 444
文中提到這段代碼是因為i不被銷毀導致的,今天搜記憶體配置的時候也看到這個東西。編程的時候應該盡量使用list comprehension減少for和while,一是函數化編程簡潔明了,另一方面是效能提升和節省一個計數器。