新聞中心
文章的主題
不要使用可變對(duì)象作為函數(shù)的默認(rèn)參數(shù)例如 list,dict,因?yàn)?code>def是一個(gè)可執(zhí)行語(yǔ)句,只有def執(zhí)行的時(shí)候才會(huì)計(jì)算默認(rèn)默認(rèn)參數(shù)的值,所以使用默認(rèn)參數(shù)會(huì)造成函數(shù)執(zhí)行的時(shí)候一直在使用同一個(gè)對(duì)象,引起bug。

成都服務(wù)器托管,成都創(chuàng)新互聯(lián)公司提供包括服務(wù)器租用、四川主機(jī)托管、帶寬租用、云主機(jī)、機(jī)柜租用、主機(jī)租用托管、CDN網(wǎng)站加速、域名注冊(cè)等業(yè)務(wù)的一體化完整服務(wù)。電話咨詢:18982081108
基本原理
在 Python 源碼中,我們使用def來(lái)定義函數(shù)或者方法。在其他語(yǔ)言中,類似的東西往往只是一一個(gè)語(yǔ)法聲明關(guān)鍵字,但def卻是一個(gè)可執(zhí)行的指令。Python代碼執(zhí)行的時(shí)候先會(huì)使用 compile 將其編譯成 PyCodeObject.
PyCodeObject 本質(zhì)上依然是一種靜態(tài)源代碼,只不過(guò)以字節(jié)碼方式存儲(chǔ),因?yàn)樗嫦蛱摂M機(jī)。因此 Code 關(guān)注的是如何執(zhí)行這些字節(jié)碼,比如棧空間大小,各種常量變量符號(hào)列表,以及字節(jié)碼與源碼行號(hào)的對(duì)應(yīng)關(guān)系等等。
PyFunctionObject 是運(yùn)行期產(chǎn)生的。它提供一個(gè)動(dòng)態(tài)環(huán)境,讓 PyCodeObject 與運(yùn)行環(huán)境關(guān)聯(lián)起來(lái)。同時(shí)為函數(shù)調(diào)用提供一系列的上下文屬性,諸如所在模塊、全局名字空間、參數(shù)默認(rèn)值等等。這是def語(yǔ)句執(zhí)行的時(shí)候干的活。
PyFunctionObject 讓函數(shù)面向邏輯,而不僅僅是虛擬機(jī)。PyFunctionObject 和 PyCodeObject 組合起來(lái)才是一個(gè)完整的函數(shù)。
下文翻譯了一篇文章,有一些很好的例子。但是由于水平有限,有些不會(huì)翻譯或者有些翻譯有誤,敬請(qǐng)諒解。如果有任何問(wèn)題請(qǐng)發(fā)郵件到 acmerfight圈gmail.com,感激不盡
主要參考資料 書(shū)籍:《深入Python編程》 大牛:shell 和 Topsky
原文鏈接
Python對(duì)于函數(shù)中默認(rèn)參數(shù)的處理往往會(huì)給新手造成困擾(但是通常只有一次)。
當(dāng)你使用“可變”的對(duì)象作為函數(shù)中作為默認(rèn)參數(shù)時(shí)會(huì)往往引起問(wèn)題。因?yàn)樵谶@種情況下參數(shù)可以在不創(chuàng)建新對(duì)象的情況下進(jìn)行修改,例如 list dict。
- >>> def function(data=[]):
- ... data.append(1)
- ... return data
- ...
- >>> function()
- [1]
- >>> function()
- [1, 1]
- >>> function()
- [1, 1, 1]
像你所看到的那樣,list變得越來(lái)越長(zhǎng)。如果你仔細(xì)地查看這個(gè)list。你會(huì)發(fā)現(xiàn)list一直是同一個(gè)對(duì)象。
- >>> id(function())
- 12516768
- >>> id(function())
- 12516768
- >>> id(function())
- 12516768
原因很簡(jiǎn)單: 在每次函數(shù)調(diào)用的時(shí)候,函數(shù)一直再使用同一個(gè)list對(duì)象。這么使用引起的變化,非?!皊ticky”。
為什么會(huì)發(fā)生這種情況?
當(dāng)且僅當(dāng)默認(rèn)參數(shù)所在的“def”語(yǔ)句執(zhí)行的時(shí)候,默認(rèn)參數(shù)才會(huì)進(jìn)行計(jì)算。請(qǐng)看文檔描述
http://docs.python.org/ref/function.html
的相關(guān)部分。
"def"是Python中的可執(zhí)行語(yǔ)句,默認(rèn)參數(shù)在"def"的語(yǔ)句環(huán)境里被計(jì)算。如果你執(zhí)行了"def"語(yǔ)句多次,每次它都將會(huì)創(chuàng)建一個(gè)新的函數(shù)對(duì)象。接下來(lái)我們將看到例子。
用什么來(lái)代替?
像其他人所提到的那樣,用一個(gè)占位符來(lái)替代可以修改的默認(rèn)值。None
- def myfunc(value=None):
- if value is None:
- value = []
- # modify value here
如果你想要處理任意類型的對(duì)象,可以使用sentinel
- sentinel = object()
- def myfunc(value=sentinel):
- if value is sentinel:
- value = expression
- # use/modify value here
在比較老的代碼中,written before “object” was introduced,你有時(shí)會(huì)看到
- sentinel = ['placeholder']
- 譯者注:太水,真的不知道怎么翻譯了。我說(shuō)下我的理解 有時(shí)邏輯上可能需要傳遞一個(gè)None,而你的默認(rèn)值可能又不是None,而且還剛好是個(gè)列表,列表不
- 可以寫(xiě)在默認(rèn)值位置,所以你需要占位符,但是用None,你又不知道是不是調(diào)用者傳遞過(guò)來(lái)的那個(gè)
正確地使用可變參數(shù)
最后需要注意的是一些高深的Python代碼經(jīng)常會(huì)利用這個(gè)機(jī)制的優(yōu)勢(shì);舉個(gè)例子,如果在一個(gè)循環(huán)里創(chuàng)建一些UI上的按鈕,你可能會(huì)嘗試這樣去做:
- for i in range(10):
- def callback():
- print "clicked button", i
- UI.Button("button %s" % i, callback)
但是你卻發(fā)現(xiàn)callback打印出相同的數(shù)字(在這個(gè)情況下很可能是9)。原因是Python的嵌套作用域只是綁定變量,而不是綁定數(shù)值的,所以callback只看到了變量i綁定的最后一個(gè)數(shù)值。為了避免這種情況,使用顯示綁定。
- for i in range(10):
- def callback(i=i):
- print "clicked button", i
- UI.Button("button %s" % i, callback)
i=i把callback的參數(shù)i(一個(gè)局部變量)綁定到了當(dāng)前外部的i變量的數(shù)值上。(譯者注:如果不理解這個(gè)例子,請(qǐng)看http://stackoverflow.com/questions/233673/lexical-closures-in-python)
另外的兩個(gè)用途local caches/memoization
- def calculate(a, b, c, memo={}):
- try:
- value = memo[a, b, c] # return already calculated value
- except KeyError:
- value = heavy_calculation(a, b, c)
- memo[a, b, c] = value # update the memo dictionary
- return value
(對(duì)一些遞歸算法非常好用)
對(duì)高度優(yōu)化的代碼而言, 會(huì)使用局部變量綁全局的變量:
- import math
- def this_one_must_be_fast(x, sin=math.sin, cos=math.cos):
- ...
這是如何工作的?
當(dāng)Python執(zhí)行一條def語(yǔ)句時(shí), 它會(huì)使用已經(jīng)準(zhǔn)備好的東西(包括函數(shù)的代碼對(duì)象和函數(shù)的上下文屬性),創(chuàng)建了一個(gè)新的函數(shù)對(duì)象。同時(shí),計(jì)算了函數(shù)的默認(rèn)參數(shù)值。
不同的組件像函數(shù)對(duì)象的屬性一樣可以使用。上文用到的'function'
- >>> function.func_name
- 'function'
- >>> function.func_code
", line 1>- >>> function.func_defaults
- ([1, 1, 1],)
- >>> function.func_globals
- {'function':
, - '__builtins__':
, - '__name__': '__main__', '__doc__': None}
這樣你可以訪問(wèn)默認(rèn)參數(shù),你甚至可以修改它。
- >>> function.func_defaults[0][:] = []
- >>> function()
- [1]
- >>> function.func_defaults
- ([1],)
然而我不推薦你平時(shí)這么使用。
另一個(gè)重置默認(rèn)參數(shù)的方法是重新執(zhí)行相同的def語(yǔ)句,Python將會(huì)和代碼對(duì)象創(chuàng)建一個(gè)新的函數(shù)對(duì)象,并計(jì)算默認(rèn)參數(shù),并且把新創(chuàng)建的函數(shù)對(duì)象賦值給了和上次相同的變量。但是再次強(qiáng)調(diào),只有你清晰地知道在做什么的情況下你才能這么做。
And yes, if you happen to have the pieces but not the function, you can use the function class in the new module to create your own function object.
原文鏈接:https://github.com/acmerfight/insight_python/blob/master/Default_Parameter.md#
新聞名稱:Python中的默認(rèn)參數(shù)值
瀏覽地址:http://m.fisionsoft.com.cn/article/dhdcdpg.html


咨詢
建站咨詢
