使用可變對象做爲python函數默認參數引起的問題

寫python的都知道,python函數或者方法能夠使用默認參數,好比python

1 def foo(arg=None):
2     print(arg)
3  
4 foo()
5   
6 foo("hello world")

 

一個很簡單的函數,參數arg默認使用None,當調用foo函數時,能夠傳入一個參數,也能夠不傳入參數,運行結果以下app

None
hello world

 

這很好理解。默認參數是python一個很好的特性。函數

 

可是若是使用可變對象做爲默認參數,就會引起問題。以前寫過一個腳本,bug不斷,後來終於找到了緣由。下面引用Fluent python中的一個例子加以說明。spa

廢話很少說,直接上代碼code

 1 """可變對象做爲默認參數傳入到函數引起的問題"""
 2 
 3 
 4 class Bus:
 5     def __init__(self, passengers=[]):
 6         self.passengers = passengers
 7 
 8     def pick(self, a_passenger):
 9         self.passengers.append(a_passenger)
10 
11     def drop(self, a_passenger):
12         self.passengers.remove(a_passenger)
13 
14 
15 if __name__ == '__main__':
16     bus1 = Bus(['Alice', 'Jeff', 'ethan'])
17     print(bus1.passengers)
18     bus1.pick("Simon")
19     print(bus1.passengers)  # 到此爲止並沒什麼問題
20 
21     # 接下來問題來了, 建立了兩個Bus對象,並進行操做。
22     bus2 = Bus()
23     print(bus2.passengers)
24     bus2.pick("Alice")   # "Alice"上了bus2
25     print(bus2.passengers)  # 打印bus2的乘客列表
26     bus3 = Bus()   # 建立另外一個Bus實例,一樣不傳入參數(使用默認[]參數)
27     print(bus3.passengers)  # 打印bus3的乘客列表
28 
29     bus3.pick("alex")
30     print(bus3.passengers)
31     print(bus2.passengers)
32     print(bus3.passengers is bus2.passengers)
33 
34     print(bus3.passengers is bus1.passengers)

 

下面來看運行結果對象

['Alice', 'Jeff', 'ethan']
['Alice', 'Jeff', 'ethan', 'Simon'] [] ['Alice'] ['Alice'] ['Alice', 'alex'] ['Alice', 'alex'] True False

 

從結果能夠看出,bus1這個對象彷佛沒什麼問題,而bus2和bus3對象的passengers屬性彷佛是同一個。爲何會這樣呢?blog

咱們先來分析下Bus這個類rem

定義了一個Bus類,有一個__init__方法,pick方法和drop方法。__init__方法有一個默認參數,即passengers=[]。it

你們應該都知道在python中列表對象([])是可變對象。 class

當建立bus1對象的時候,傳入了實實在在的passengers參數(一個非空列表)

當建立bus2和bus3對象的時候,沒有傳入參數,因此會使用默認參數([])

而後我對這幾個Bus實例進行操做(調用pick或drop方法,即插入或者刪除passengers列表中的乘客)

從結果能夠很直觀的看出來,bus1.passengers 並沒什麼問題,一切按照咱們的預期進行。可是bus2的乘客"Alice"爲何會出如今bus3上? bus3的乘客「alex」爲何又會出如今bus2上???

 

其實這就是用可變對象做爲默認參數引起的後果。由於當使用默認參數定義了一個函數/方法以後,加載模塊的時候已經初始化了這個可變對象,因此當你調用這個函數/方法的時候,若是你未傳入參數(即便用了默認參數), 則對函數/方法的不一樣調用,其實傳入的是同一個可變對象(上面例子中是一個空列表)。 

下面請看

 1 class Bus:
 2     def __init__(self, passengers=[]):
 3         self.passengers = passengers
 4  
 5     def pick(self, a_passenger):
 6         self.passengers.append(a_passenger)
 7  
 8     def drop(self, a_passenger):
 9         self.passengers.remove(a_passenger) 
10 
11 if __name__ == '__main__':
12     print(Bus.__init__.__defaults__)

 

 

運行結果

([],)

 

在這個例子中,並無初始化Bus類, 而__init__方法的__defaults__屬性,是一個空列表, 也就是說,在完成__init__方法定義的時候,就已經初始化了默認參數(一個空列表), 由於列表是可變對象,因此後續對Bus類的引用就會引起問題。

至此,問題緣由很明確了,就是可變對象致使了這個問題,所以千萬不要使用可變對象做爲python函數/方法的默認參數, 通常建議使用None做爲默認參數,即
1 def foo(arg=None):
2      pass
相關文章
相關標籤/搜索