看到了有給Python 函數參數的預設值傳遞可變對象,以此來加快斐波那契函數的遞歸速度,程式碼如下:
def fib(n, cache={0: 0, 1: 1}): if n not in cache: cache[n] = fib(n - 1) + fib(n - 2) return cache[n]
是不是很新奇,居然可以這樣,速度真的非常快,運行結果如下:
不過,我勸你不要這樣做,而且IDE 也會提示你這樣做很不好:
這是因為,萬物皆對象,Python 函數也是對象,參數的預設值就是對象的屬性,在編譯階段參數的預設值就已經綁定到該函數,如果是可變對象,Python 函數參數的預設值在會被存儲,並被所有的呼叫者共享,也就是說,一個函數的參數預設值如果是一個可變對象,例如List、Dict,呼叫者A 修改了它,那麼之後呼叫者B 在呼叫的時候看到的就是A 修改後的結果,這樣的模式往往會產生意想不到的結果,例如上面fib 的演算法,但更多的是bug。
可以看下這段簡單的程式碼:
def func(n, li = []): for i in range(n): li.append(i) print(l) func(2) # [0,1] func(3,l=[1,2]) # [1,2,0,1,2] func(2) # [0,1]
你可以先估算這段程式碼的輸出,如果跟註解中的一樣,那你就錯了。正確的結果是:
[0, 1] [1, 2, 0, 1, 2] [0, 1, 0, 1]
你可能會覺得,最後一個func(2) 怎麼是這樣,不急,我們print(id(li)) 調試一下:
def func(n, li = []): print(id(li)) for i in range(n): li.append(i) print(li) func(2) func(3,li=[1,2]) func(2)
結果如下:
140670243756736 [0, 1] 140670265684928 [1, 2, 0, 1, 2] 140670243756736 [0, 1, 0, 1]
有沒有發現,第一個func(2) 和第二個func(2) 的id 是一樣的,表示它們用到的是li 是同一個,這就參數的預設值是可變物件的邏輯,對於所有的呼叫者來講,是共享的。
如果要深入研究 Python 為什麼這麼設計,可以移步 http://cenalulu.github.io/python/default-mutable-arguments/
#如何避免?
最好的方式是不要使用可變物件作為函數預設值。如果非要這麼用的話,以下是一種解決方案:
def generate_new_list_with(my_list=None, element=None): if my_list is None: my_list = [] my_list.append(element) return my_list
這樣,如果 my_list 預設值永遠都是 []。
我想那個fib 函數的實作可能會讓你印象深刻,不過請注意,這樣的用法非常危險,不可用於自己的程式碼中。
以上是Python 函數參數的預設值為可變物件時需要小心的詳細內容。更多資訊請關注PHP中文網其他相關文章!