今天,看到一道python題,初始覺得很簡單,但是看到最終答案的我一臉懵逼,先把題列出來看看:
def func(n, li = []):
for i in range(n):
li.append(i)
print(l)
func(2)
func(3,l=[1,2])
func(2)
看到這里的小伙伴可以先默算一下結(jié)果。
我想的答案是三行分別打?。篬0,1]、[1,2,0,1,2]、[0,1]
但是:
# 輸出:
[0, 1]
[1, 2, 0, 1, 2]
[0, 1, 0, 1]
???
???????
最后一個什么情況?為什么是在第一次調(diào)用函數(shù)后的li列表中添加的?
基于自己對內(nèi)存地址的理解,我知道當(dāng)前情況說明的是:第一次和第三次函數(shù)調(diào)用時,函數(shù)內(nèi)部的li列表都是同一個內(nèi)存地址,并未引用新的內(nèi)存空間;而第二次調(diào)用函數(shù)時,指定了列表參數(shù),此時li引用了新的內(nèi)存空間。
所以我在函數(shù)內(nèi)打印了列表參數(shù)li的內(nèi)存地址:
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)
輸出:
2017077218568
[0, 1]
2017076682696
[1, 2, 0, 1, 2]
2017077218568
[0, 1, 0, 1]
果然,第一次調(diào)用和第三次調(diào)用時其操作的是同一個內(nèi)存地址數(shù)據(jù),因此造成了這樣的追加結(jié)果。
最可疑的原因是默認參數(shù)引起的,不過已經(jīng)超過我的認知,為什么內(nèi)部有這種現(xiàn)象,自己真想不明白,求助google找到了答案。
原因解析
源地址答案:Python函數(shù)參數(shù)默認值的陷阱和原理深究:http://cenalulu.github.io/python/default-mutable-arguments/
這里首先需要理解python中變量的實質(zhì),了解其引用和內(nèi)存地址的含義。
Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well. ——Python Common Gotchas
其大意是說,Python的默認參數(shù)只在函數(shù)定義時被賦值一次,而不會每次調(diào)用函數(shù)時又創(chuàng)建新的引用。
這意味著,函數(shù)定義完成后,默認參數(shù)已經(jīng)存在固定的內(nèi)存地址了,如果你使用一個可變的默認參數(shù)并對其進行改變,那么以后對該函數(shù)的調(diào)用都會改變這個可變對象。
原文作者解釋如下:
可見如果參數(shù)默認值是在函數(shù)編譯
compile階段就已經(jīng)被確定。之后所有的函數(shù)調(diào)用時,如果參數(shù)不顯示的給予賦值,那么所謂的參數(shù)默認值不過是一個指向那個在compile階段就已經(jīng)存在的對象的指針。如果調(diào)用函數(shù)時,沒有顯示指定傳入?yún)?shù)值得話。那么所有這種情況下的該參數(shù)都會作為編譯時創(chuàng)建的那個對象的一種別名存在。如果參數(shù)的默認值是一個不可變(Imuttable)數(shù)值,那么在函數(shù)體內(nèi)如果修改了該參數(shù),那么參數(shù)就會重新指向另一個新的不可變值。而如果參數(shù)默認值是和本文最開始的舉例一樣,是一個可變對象(Muttable),那么情況就比較糟糕了。所有函數(shù)體內(nèi)對于該參數(shù)的修改,實際上都是對compile階段就已經(jīng)確定的那個對象的修改。
Python官方文檔中也有特別提示:Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:
如何避免這個缺陷
當(dāng)然最好的方式是不要使用可變對象作為函數(shù)默認值。如果非要這么用的話,下面是一種解決方案。還是以文章開頭的需求為例:
def func(n, li = []):
# 這里使用 is None判斷不行
if not li:
li = []
print(id(li))
for i in range(n):
li.append(i)
print(li)
func(2)
func(3,li=[1,2])
func(2)
輸出結(jié)果,能按照正常邏輯結(jié)果輸出:
2017078756808
[0, 1]
2017079467976
[1, 2, 0, 1, 2]
2017078756808
[0, 1]
結(jié)語
這是設(shè)計python語言的時候就定義好的奇異之處,雖說有點違背編程邏輯,但記得避免就好,不用過多深究。
2019.07.07更新:遇到類似的問題一并總結(jié)在這篇了
函數(shù)可能會修改接收到的任何可變對象
def f(a,b):