陷阱?
學過函數的人一定聽說過函數的默認參數,關於函數的默認參數,請看以下的例子:
def extendList(val, lst=[]): lst.append(val) return lst list1 = extendList(10) list2 = extendList(123, []) print('list1 = %s' % list1) print('list2 = %s' % list2)
打印的結果是 現在,我們將代碼再添加一處,來看看最后的結果是什么:
def extendList(val, lst=[]): lst.append(val) return lst list1 = extendList(10) list2 = extendList(123, []) list3 = extendList('a') print('list1=%s' % list1) print('list2=%s' % list2) print('list3=%s' % list3)
當list1處調用函數時,10被加入了列表;list2處調用函數,123被加入到了新傳入的列表中;最后到list3調用函數,應該將‘a’繼續加入到列表中返回。因此得到的輸出應該是:
# list1 = [10] # list2 = [233] # list3 = ['a']
陷阱!
然而,實際的打印結果變成了:
陷阱之所以稱之為陷阱,代表我們不能以普通的思維來看待它,通過查閱資料,得到以下的一句解釋:
A new list is created once when the function is defined, and the same list is used in each successive call.
在定義函數時,Python的默認參數會被計算一次,而不是每次調用函數時(比如Ruby)。這意味着如果你使用一個可變的默認參數並對其進行改變,那么你將會直接修改該對象,該影響將一直延續到未來關於該函數的調用(在默認參數沒有被重新賦其他值的情況下)。
眾所周知,Python變量存儲的是變量和值的引用關系,即實際變量對應一個內存地址。這意味着Python函數總是通過地址傳遞(傳遞參數)工作。調用函數時,不會將參數值復制到函數占位符。相反,我們將占位符指向變量本身。這有一個非常重要的結果:我們可以從函數內部更改變量的值。
如何避開陷阱?
None通常是一個不錯的選擇:
def extendList(val, lst = None): if not lst: lst = [] lst.append(val) return lst
有時您可以專門利用此陷阱來維護函數調用之間的狀態。這通常在編寫緩存函數時完成。
參考資料:https://docs.python-guide.org/writing/gotchas/
http://blog.thedigitalcatonline.com/blog/2015/02/11/default-arguments-in-python/