函數內部的變數在函數執行完後就銷毀,為什麼可變對象卻能保存上次調用時的結果呢??
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()
然後,在命令行里,這樣搞一發:
其他部分略。 可以看到,這段代碼的位元組碼是這樣的,上來先創建一個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 更多編程相關的內容,請關注我的公眾號: 我的公眾號&>&>&> f = open("testfoo.py")
&>&>&> s = f.read()
&>&>&> co = compile(s, "&
&>&>&> import dis
&>&>&> dis.dis(co)
1 0 BUILD_LIST 0
3 LOAD_CONST 0 (&)
6 MAKE_FUNCTION 1
9 STORE_NAME 0 (foo)
這是 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了再恢復?