1.用一個可變的值作為默認(rèn)值
這是一個絕對值得放在第一個來說的問題。不僅僅是因?yàn)楫a(chǎn)生這種BUG的原因很微妙们豌,而且這種問題也很難檢查出來萝映。思考一下下面的代碼片段:
def foo(numbers=[]):
numbers.append(9)
print numbers
在這里鳄炉,我們定義了一個 list (默認(rèn)為空),給它加入9并且打印出來烟勋。
>>> foo()
[9]
>>> foo(numbers=[1,2])
[1, 2, 9]
>>> foo(numbers=[1,2,3])
[1, 2, 3, 9]
看起來還行吧规求?可是當(dāng)我們不輸入number 參數(shù)來調(diào)用 foo 函數(shù)時(shí),神奇的事情發(fā)生了:
>>> foo() # first time, like before
[9]
>>> foo() # second time
[9, 9]
>>> foo() # third time...
[9, 9, 9]
>>> foo() # WHAT IS THIS BLACK MAGIC?!
[9, 9, 9, 9]
那么卵惦,這是神馬情況阻肿?直覺告訴我們無論我們不輸入 number 參數(shù)調(diào)用 foo 函數(shù)多少次,
這里的9應(yīng)該被分配進(jìn)了一個空的 list沮尿。這是錯的丛塌!在Python里,函數(shù)的默認(rèn)值實(shí)在函數(shù)定義的時(shí)候?qū)嵗男蠹玻皇窃谡{(diào)用的時(shí)候赴邻。
那么我們?nèi)匀粫枺瑸槭裁丛谡{(diào)用函數(shù)的時(shí)候這個默認(rèn)值卻被賦予了不同的值啡捶?因?yàn)樵谀忝看谓o函數(shù)指定一個默認(rèn)值的時(shí)候姥敛,
Python都會存儲這個值。如果在調(diào)用函數(shù)的時(shí)候重寫了默認(rèn)值瞎暑,那么這個存儲的值就不會被使用彤敛。當(dāng)你不重寫默認(rèn)值的時(shí)候与帆,
那么Python就會讓默認(rèn)值引用存儲的值(這個例子里的numbers)。它并不是將存儲的值拷貝來為這個變量賦值臊泌。
這個概念可能對初學(xué)者來說鲤桥,理解起來會比較吃力,所以可以這樣來理解:有兩個變量渠概,一個是內(nèi)部的茶凳,一個是當(dāng)前運(yùn)行時(shí)的變量。
現(xiàn)實(shí)就是我們有兩個變量來用相同的值進(jìn)行交互播揪,所以一旦 numbers 的值發(fā)生變化贮喧,也會改變Python里面保存的初始值的記錄。
那么解決方案如下:
def foo(numbers=None):
if numbers is None:
numbers = []
numbers.append(9)
print numbers
通常猪狈,當(dāng)人們聽到這里箱沦,大家會問另一個關(guān)于默認(rèn)值的問題。思考下面的程序:
def foo(count=0):
count += 1
print count
當(dāng)我們運(yùn)行它的時(shí)候雇庙,其結(jié)果完全是我們期望的:
>>> foo()
1
>>> foo()
1
>>> foo(2)
3
>>> foo(3)
4
>>> foo()
1
這又是為啥呢谓形?其秘密不在與默認(rèn)值被賦值的時(shí)候,而是這個默認(rèn)值本身疆前。整型是一種不可變的變量寒跳。跟 list 類型不同,在函數(shù)執(zhí)行的過程中竹椒,整型變量是不能被改變的童太。當(dāng)我們執(zhí)行 count+=1 這句話時(shí),我們并沒有改變 count 這個變量原有的值胸完。而是讓 count 指向了不同的值书释。可是赊窥,當(dāng)我們執(zhí)行 numbers.append(9) 的時(shí)候爆惧,我們改變了原有的 list 。因而導(dǎo)致了這種結(jié)果誓琼。
下面是在函數(shù)里使用默認(rèn)值時(shí)會碰到的另一種相同問題:
def print_now(now=time.time()):
print now
跟前面一樣检激,time.time() 的值是可變的,那么它只會在函數(shù)定義的時(shí)候計(jì)算腹侣,所以無論調(diào)用多少次叔收,都會返回相同的時(shí)間 — 這里輸出的時(shí)間是程序被Python解釋運(yùn)行的時(shí)間。
>>>print_now()
1373121487.91
>>> print_now()
1373121487.91
>>>print_now()
1373121487.91
這個問題和它的解決方案在 Python 2.x 和 3.x 里都是類似的傲隶,在Python 3.x 里面唯一的不同饺律,是里面的print 表達(dá)式應(yīng)該是函數(shù)調(diào)用的方式(print(numbers))。
大家應(yīng)該注意到我在解決方案里用了 if numbers is None 而不是 if not numbers 跺株。這是另一種常見的錯誤复濒,我準(zhǔn)備在接下來的文章里面介紹脖卖。