本文主要內容翻譯自:點擊打開鏈接
Python處理默認參數值的方式是少數幾個難住Python初學者的問題之一(當然通常也就難住第一次)。
造成這種困惑的主要原因是當你使用一個可變的對象作為默認參數值時,也就是說,這個默認值會在某些時候被修改,比如一個list對象或者一個dictionary對象。 舉一個例子:
>>> def function(data=[]):... data.append(1)... return data...>>> function()[1]>>> function()[1, 1]>>> function()[1, 1, 1]可以看到,打印出來的list會變得越來越長。如果你注意一下這個list的Identity,你會發現這個函數一直在返回同樣的值:
>>> id(function())12516768>>> id(function())12516768>>> id(function())12516768原因很簡單,這個函數在每次調用中,一直在使用同樣的對象,我們做的修改是具有“粘行的”。
為什么會發生這樣的事兒? 當且僅當默認參數值屬于的“def”語句被執行的時候,它們就會被求值。
同時也要注意,在Python中,“def”定義的語句是可執行的,它的默認參數會在相對應的“def”語句環境中被求值。如果你多次運行“def”語句,每次它都會創建新的函數對象(用新計算的默認值)。我們將在下面看到例子。
正確的處理方式是怎樣的? 作為一種變通方案,正如其他人提到的那樣,使用一個占位符而不是修改這個默認值。None是一個常見的占位符:
def myfunc(value=None): if value is None: value = [] # modify value here如果你需要處理任意對象,包括None,你可以使用一個哨兵對象:
sentinel = object()def myfunc(value=sentinel): if value is sentinel: value = exPRession # use/modify value here在老的代碼中,也就是在寫“object”之前被介紹到的,你有時候會看到如下這樣的寫法:
sentinel = ['placeholder']通常用一個唯一的Identity來創建一個non-false對象;當被求值的時候,[] 每次都會創建一個新的list。
使用可變默認參數的有效方式 最后,應該注意到的是在更高級的Python代碼中經常使用這個機制來體現它的優勢;例如:如果你想在一個循環中創建一串UI Button,你可能會這樣嘗試:
for i in range(10): def callback(): print "clicked button", i UI.Button("button %s" % i, callback)不料卻發現所有的callback都打印相同的值,在這個例子中最可能就是9. 原因是Python的嵌套域綁定在variable上,而不是object value,所以所有的callback實例都將看到當前 i 這個變量的值(=最后一個值)。要修正這個例子,我們只需使用明確的綁定:
for i in range(10): def callback(i=i): print "clicked button", i UI.Button("button %s" % i, callback)在“i = i”這部分,綁定參數“i”(一個本地變量)到外部變量“i”的當前值中。
(這里我想提一下,Python中“def”定義的函數其實就是一個對象,我們可以把函數具有的默認參數值理解成這個對象的“類屬性”,而當這個屬性是可變對象的時候,那么我們所做的操作就會被保留下來。再看下面這個例子:
def bar(a=[]): print id(a) a = a + [1] #賦值后創建了一個新的對象a print id(a) return a>>> bar()44843702324484524224[1]>>> bar()44843702324484524152[1]>>> bar()4484370232 # "類屬性"一直都沒有改變4484523720 # 總是一個新的對象 [1]>>> id(bar.func_defaults[0])4484370232這樣應該就好理解多了,在下面的例子中還會提到這點。) 還有兩個其他的使用場景是本地緩存和記憶化,例如:
def calculate(a, b, c, memo={}): try: value = memo[a, b, c] # 返回已經計算的值 except KeyError: value = heavy_calculation(a, b, c) memo[a, b, c] = value # 更新memo字典 return value(在確定類型的遞歸算法中這是相當好用的) 還有就是,在高度優化的代碼中,local name重新綁定到global name上:
import mathdef this_one_must_be_fast(x, sin=math.sin, cos=math.cos): ...這種特性是如何工作的? 當Python運行一個”def“語句時,它帶來了一些已經準備好的部件(包括為這個函數體編譯好的代碼和當前的命名空間),而且創建了一些新的函數對象。當它做這些的時候,它也會計算默認參數值。
各種各樣的組成元件是這個函數對象的有效屬性,我們使用上面提到的函數來演示:
>>> function.func_name'function'>>> function.func_code<code object function at 00BEC770, file "<stdin>", line 1>>>> function.func_defaults([1, 1, 1],)>>> function.func_globals{'function': <function function at 0x00BF1C30>,'__builtins__': <module '__builtin__' (built-in)>,'__name__': '__main__', '__doc__': None}既然你可以訪問這些默認值,那么你也可以修改他們:
>>> function.func_defaults[0][:] = []>>> function()[1]>>> function.func_defaults([1],)然而,這不是直接的處理方式,更推薦常規的使用方法。。。 (注意這里”[:]”的用法,舉個例子:
>>> a = [x for x in range(8)]>>> print(id(a))4320183880>>> a = a + [11, 12]>>> print(id(a))4320210632 #賦了新值后地址變了>>> a[:] = a + [13, 14]>>> print(id(a))4320210632 #同樣是賦值但是地址沒變>>> print(a)[0, 1, 2, 3, 4, 5, 6, 7, 11, 12, 13, 14]當同一個list在很多地方都在引用的時候,我們對某個做的賦值,應該考慮是否讓其他地方也適用)
另外一種重置默認參數的簡單方法是重新運行相同的”def“語句。然后Python將為代碼對象創建一個新的綁定,計算默認參數的值,賦值這個函數對象到前面同樣的變量上。但是賦值的時候,你得清楚的知道你在做什么。
當然,如果你碰巧有這個pieces而不是function,你可以使用 new 模塊中的 function 類,來創建你自己的函數對象。
新聞熱點
疑難解答