寫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