函數的默認參數使得函數的調用變得簡單。實際上,默認參數的值只在定義時計算一次,因此每次使用默認參數調用函數時,得到的默認參數值是相同的。我們看一個例子。
import datetime as dt from time import sleep def log_time(msg,time = dt.datetime.now()): sleep(1) print("%s:%s"%(time.isoformat(),msg)) log_time("msg 1") log_time("msg 2") log_time("msg 3")
結果:
三次調用函數得到的默認值是一模一樣,而且中間讓程序睡眠了1秒,可以排除是程序運行太快的因素,因此這足以說明函數的默認值只在定義的時候計算了一次,以后每次調用用到默認值時都是用的定義時計算的值。
可變參數與不可變參數
當默認參數是可變參數時:
1 def bad_append(new_item,a_list =[]): #列表是可變數據類型 2 print("address of a_list:",id(a_list)) 3 a_list.append(new_item) 4 return a_list 5 6 print(bad_append("1")) # ["1"] 7 print(bad_append("2")) # ["1","2"]
我們期待結果是["1"] 和 ["2"],但實際是["1"]和["1","2"],這是因為,函數參數只在定義的時候計算一次,在定義時參數a_list的內存你地址已經分配好了,由於列表是可變數據類型,改變數據不會重新創建對象,也就沒有內存地址的重新分配,所以在以后調用時,只要不給默認參數重新賦值,它的內存地址都不會改變。兩次調用用的是同一個內存地址,操作的是同一個列表。
當參數時不可變數據類型時:
def immutable_test(i =1): print("befor,adress of i",id(i)) i += 1 print("after,address of i",id(i)) return i print(immutable_test()) print(immutable_test())
結果:
很明顯第二次調用沒有受第一次調用的影響,因為 i 是不可變數據類型,對它的操作會使內存重新分配,重新創建一個對象。那么函數中i += 1之后名字i指向了另外的地址;根據默認參數的規則,下次調用時,i指向的地址還是函數定義時賦予的地址,這個地址的值1並沒有被改變。
結論:
默認參數為不可變數據類型時,多次調用不會造成任何影響;為可變數據類型時,多次調用的結果不符合預期。
因此,在將可變數據類型作為默認參數時,就不能只在函數定義時初始化一次,而應該在每次調用時初始化。
最佳實踐是定義函數時指定可變默認參數的值為None,在函數體內部重新綁定默認參數的值。以下是對上面的兩個可變默認參數示例最佳實踐的應用:

def good_append(new_item,a_list = None): if a_list is None: a_list = [] a_list.append(new_item) return a_list print(good_append("1")) #['1'] print(good_append("2")) #['2'] print(good_append("c",["a","b"])) #['a', 'b', 'c'] def log_time(msg,time = None): if time is None: time = dt.datetime.now() sleep(1) print("%s:%s"%(time.isoformat(),msg)) log_time("msg1") #2018-10-31T19:44:34.117109:msg1 log_time("msg2") #2018-10-31T19:44:35.117166:msg2