函數內部的變數在函數執行完後就銷毀,為什麼可變對象卻能保存上次調用時的結果呢??

def foo(x,items=[]):

items.append(x)

return items

foo(1)

foo(2)

foo(3)

返回值是

[1]

[1,2]

[1,2,3]

第一次執行foo(1)後,函數就結束了,那麼變數items也被銷毀了,第二次再執行foo(2)時,按理items應該是一個新創建的變數,在內存中是新的內存空間,怎麼還會保存上一次調用的結果呢??


授人以魚,不如授人以漁。

Python里有許多這種奇奇怪怪的問題,遇到不理解的地方,最好的辦法就是學會去看Python位元組碼。我講下怎麼看位元組碼。

例如,我們把這段程序存為testfoo.py:

def foo(l = []):
l.append(1)
print l

foo()
foo()

然後,在命令行里,這樣搞一發:

&>&>&> f = open("testfoo.py")
&>&>&> s = f.read()
&>&>&> co = compile(s, "&", "exec")
&>&>&> import dis
&>&>&> dis.dis(co)
1 0 BUILD_LIST 0
3 LOAD_CONST 0 (&)
6 MAKE_FUNCTION 1
9 STORE_NAME 0 (foo)

其他部分略。

可以看到,這段代碼的位元組碼是這樣的,上來先創建一個list(BUILD_LIST),然後,把foo所對應的code object載入到棧頂,然後再執行MAKE_FUNCTION。注意看,MAKE_FUNCTION的位元組碼是帶參數的,參數為1。那麼它的真實動作呢,是創建一個FunctionObject,這個FuntionObject的靜態代碼就是CodeObject,默認參數就是剛才創建的list。

OK,也就是說,在執行函數定義的時候,python內部創建了一個FunctionObject,還創建了一個list,並且把這兩個對象綁在一起了。所以,每次調用不帶參數的foo,那個 l 就是 foo 上所綁定的list對象。

所以,理解這個問題的關鍵在於理解,Python的函數是由語句執行時創建的,以及FunctionObject與默認參數的綁定問題。

為什麼在函數創建時就綁定,而不在函數調用時綁定呢?因為這樣寫,編譯器的實現簡單啊。我正在用Java寫一個玩具的Python虛擬機。打算修正這個問題,修正的辦法就是在生成位元組碼的時候,把BUILD_LIST放到 foo 里去做,而不是外面,這樣就保證了每次調用都會得到一個新的list。

這個工程在這裡:

hinus/railgun

更多編程相關的內容,請關注我的公眾號:

我的公眾號


這是 Python 語言一個著名的坑,基本上,像樣兒的教程里都會提及。

你的腳本是:

def foo(x,items=[]):
items.append(x)
return items

foo(1)
foo(2)
foo(3)

腳本載入執行過程中,運行到 def foo(x, items=[]) 這一行有沒有執行? 有!

Python 虛擬機生成了一個 funciton 對象叫作 foo;並生成了一個空list對象,沒有名字,我們不妨叫做 anonymous_list,它是全局對象,如果items沒有設定參數,就會指向它。

執行到 foo(1),發現items沒有實參,則使用 anonymous_list,現在變成了 [1]。

執行到 foo(2),發現items沒有實參,繼續使用 anonymous_list,現在變成了 [1, 2]。

執行到 foo(3),發現items沒有實參,繼續使用 anonymous_list,現在變成了 [1, 2, 3]。

所以就成了這個鬼樣子。

變通的辦法是,用 None 代替 [ ]。

def foo(x, items=None):
if items is None:
items = []
items.append(x)
return items

我覺得Python之父應該把這個改過來,因為違反直覺,又跟其它語言表現不一樣。


這種東西我們一般叫bug


為啥要給打上函數式編程的標籤……

這玩意對參數都有副作用……


這是默認參數的坑,原因見其他回答

我只是吐槽下 看到有函數式編程的tag 點進來前以為是在說閉包


離開函數作用域時,只有變數的引用被「銷毀」了,就像你撕掉一張名單,名單上的人也不會死一樣。在這裡由於函數參數默認值一直引用同一個對象,所以這個列表永遠不會被銷毀,它的狀態也會一直保存到下一次函數調用。


這個python參數的默認值陷阱問題, 已經有網友已經分析過了 Python函數參數默認值的陷阱和原理深究

你自行去看吧. (又被邀請了, 奇怪)


你看一下值和引用的區別,再看一下深拷貝淺拷貝,然後就有概念了


推薦閱讀:

Python 對異常與錯誤的處理策略,用 try...except,還是 if...else...,哪種比較好?
python numpy 數組如何對每個元素進行操作?
python 中如何實現一行輸入多個值 ?
為什麼看不懂廖雪峰的Python學習教程?
Python中 pickle有什麼意義,pickle了再恢復?

TAG:Python | 函數式編程 | Python入門 |