小心此坑:Python 函數(shù)參數(shù)的默認(rèn)值是可變對象
看到了有給 Python 函數(shù)參數(shù)的默認(rèn)值傳遞可變對象,以此來加快斐波那契函數(shù)的遞歸速度,代碼如下:
是不是很新奇,居然可以這樣,速度真的非常快,運行結(jié)果如下:
不過,我勸你不要這樣做,而且 IDE 也會提示你這樣做很不好:
這是因為,萬物皆對象,Python 函數(shù)也是對象,參數(shù)的默認(rèn)值就是對象的屬性,在編譯階段參數(shù)的默認(rèn)值就已經(jīng)綁定到該函數(shù),如果是可變對象,Python 函數(shù)參數(shù)的默認(rèn)值在會被存儲,并被所有的調(diào)用者共享,也就是說,一個函數(shù)的參數(shù)默認(rèn)值如果是一個可變對象,例如 List、Dict,調(diào)用者 A 修改了它,那么之后調(diào)用者 B 在調(diào)用的時候看到的就是 A 修改后的結(jié)果,這樣的模式往往會產(chǎn)生意想不到的結(jié)果,比如上面 fib 的算法,但更多的是 bug。
可以看下這段簡單的代碼:
你可以先估算一下這段代碼的輸出,如果和注釋中的一樣,那你就錯了。正確的結(jié)果是:
你可能會覺得,最后一個 func(2) 怎么是這樣,不急,我們 print(id(li)) 調(diào)試一下:
結(jié)果如下:
有沒有發(fā)現(xiàn),第一個 func(2) 和第二個 func(2) 的 id 是一樣的,說明它們用到的是 li 是同一個,這就參數(shù)的默認(rèn)值是可變對象的邏輯,對于所有的調(diào)用者來講,是共享的。
如果要深入研究 Python 為什么這么設(shè)計,可以移步 http://cenalulu.github.io/python/default-mutable-arguments/
如何避免?
最好的方式是不要使用可變對象作為函數(shù)默認(rèn)值。如果非要這么用的話,下面是一種解決方案:
這樣,如果 my_list 默認(rèn)值永遠(yuǎn)都是 []。
最后
我想那個 fib 函數(shù)的實現(xiàn)可能會讓你印象深刻,不過請注意,這樣的用法非常危險,不可用于自己的代碼中。