Python 使用 list 作為函數參數時,參數是否會初始化?

看到了這樣一段代碼:

def foo(a, b=[]):
b.append(a)
print b

運行foo(1),得出的反饋是[1];

再次運行foo(2),得出的反饋是[1,2]。

我之前的理解是,每一次運行函數的時候,似乎都會初始化參數的值?為什麼這裡的b在兩次調用之間始終保留之前的運算結果呢?請各位達人指教,謝謝!


&>&>&> def foo(bar=[]):
... return bar
&>&>&> foo.func_name
"foo"
&>&>&> foo.func_defaults
([],)
&>&>&> foo() is foo.func_defaults[0]
True

所以默認參數和函數對象本身是一一對應的,一個函數擁有一個默認參數的 tuple。這也很好理解,默認參數隨函數一起定義,並且出現在函數簽名里,理所應當是函數的一部分。

問題的點在於 Python 中對象傳遞全部為「引用」,list 對象又都是 mutable 的,所以函數調用時往 list 對象作為的默認參數中 append 會造成額外副作用。

事實上,Python 開發是倡導用 immutable 對象作為默認參數的。比如用 None 就是個不錯的選擇:

def foo(bar=None):
bar = bar or []

又或者用了 mutable 參數一定記得創建副本:

def foo(bar=[]):
bar = list(bar)


官方文檔解釋在這裡:default args 的求值是在定義的時候,只做一次

4. More Control Flow Tools

但是……

&>&>&> def f(a, b=[]):
... b.append(a)
... print b
...
&>&>&> f(1)
[1]
&>&>&> f(1)
[1, 1]
&>&>&> def f(a, b=None):
... b = b if b is not None else []
... b.append(a)
... print b
...
&>&>&> f(1)
[1]
&>&>&> f(1)
[1]
&>&>&> f(1)
[1]
&>&>&> a = []
&>&>&> b = []
&>&>&> a.append(1)
&>&>&> b
[]
&>&>&>

怎麼都覺得這 TM 是個 bug 啊……


no, def foo(a=[]) 這種函數參數寫法叫 參數默認值,只會在函數聲明是初始化一次。之後不會再變了。

note, 建議了解一下 def foo(a=[])和 foo(a=[])的區別:前者是參數默認值,後者是keyword arguments. 還有這種def foo(*args, **kargs) 和 這種 foo(*args, **kargs), 都是有細微區別的。


啥都不用說,加一個id()輸出b的所謂的地址,你就明白了


這和初始化沒有關係,當使用可變(mutable)數據結構(如列表)用作函數參數時,該列表是會被改變的。因為當兩個變數同時引用一個列表時,它們的確是同時引用一個列表。


《python語言及其應用》中也提及了這個問題和規避方法,但是沒有解釋原理。


不會的, 默認值之間是共享的, 只會創建一次, 並不會每次創建一個新的對象. 也就是說使用可變對象作為函數的默認值時會導致函數的混亂. 同理使用字典作為默認參數,會得出類似的返回.

def foo(k,v, fdict={}):
fdict[k] = v
print fdict
foo(1,2)
foo(3,4)

測試方法很簡單, 如知友所說加上 id().


推薦閱讀:

怎麼去實現一個簡單文本編輯器?
使用IDE會不會降低程序員的智商?
怎樣才能做到編程語言的「一通百通」?
學習一門新的編程語言有什麼推薦的輪子可以拿來練手的?
程序員平時沒事,做什麼?

TAG:編程語言 | Python | 編程 |