函數默認參數的初始化問題


函數的默認參數使得函數的調用變得簡單。實際上,默認參數的值只在定義時計算一次,因此每次使用默認參數調用函數時,得到的默認參數值是相同的。我們看一個例子。

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
最佳實踐

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM