Python中的默認(rèn)參數(shù)詳解
文章的主題
不要使用可變對(duì)象作為函數(shù)的默認(rèn)參數(shù)例如 list,dict,因?yàn)閐ef是一個(gè)可執(zhí)行語句,只有def執(zhí)行的時(shí)候才會(huì)計(jì)算默認(rèn)默認(rèn)參數(shù)的值,所以使用默認(rèn)參數(shù)會(huì)造成函數(shù)執(zhí)行的時(shí)候一直在使用同一個(gè)對(duì)象,引起bug。
基本原理
在 Python 源碼中,我們使用def來定義函數(shù)或者方法。在其他語言中,類似的東西往往只是一一個(gè)語法聲明關(guān)鍵字,但def卻是一個(gè)可執(zhí)行的指令。Python代碼執(zhí)行的時(shí)候先會(huì)使用 compile 將其編譯成 PyCodeObject.
PyCodeObject 本質(zhì)上依然是一種靜態(tài)源代碼,只不過以字節(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)起來。同時(shí)為函數(shù)調(diào)用提供一系列的上下文屬性,諸如所在模塊、全局名字空間、參數(shù)默認(rèn)值等等。這是def語句執(zhí)行的時(shí)候干的活。
PyFunctionObject 讓函數(shù)面向邏輯,而不僅僅是虛擬機(jī)。PyFunctionObject 和 PyCodeObject 組合起來才是一個(gè)完整的函數(shù)。
下文翻譯了一篇文章,有一些很好的例子。但是由于水平有限,有些不會(huì)翻譯或者有些翻譯有誤,敬請(qǐng)諒解。如果有任何問題請(qǐng)發(fā)郵件到 acmerfight圈gmail.com,感激不盡
主要參考資料 書籍:《深入Python編程》 大牛:shell 和 Topsky
Python對(duì)于函數(shù)中默認(rèn)參數(shù)的處理往往會(huì)給新手造成困擾(但是通常只有一次)。
當(dāng)你使用“可變”的對(duì)象作為函數(shù)中作為默認(rèn)參數(shù)時(shí)會(huì)往往引起問題。因?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變得越來越長。如果你仔細(xì)地查看這個(gè)list。你會(huì)發(fā)現(xiàn)list一直是同一個(gè)對(duì)象。
>>> id(function())
12516768
>>> id(function())
12516768
>>> id(function())
12516768
原因很簡單: 在每次函數(shù)調(diào)用的時(shí)候,函數(shù)一直再使用同一個(gè)list對(duì)象。這么使用引起的變化,非?!皊ticky”。
為什么會(huì)發(fā)生這種情況?
當(dāng)且僅當(dāng)默認(rèn)參數(shù)所在的“def”語句執(zhí)行的時(shí)候,默認(rèn)參數(shù)才會(huì)進(jìn)行計(jì)算。請(qǐng)看文檔描述
https://docs.python.org/2/reference/compound_stmts.html#function-definitions
其中有下面一段
"Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call. This is especially important to understand when a default parameter is a mutable object, such as a list or a dictionary: if the function modifies the object (e.g. by appending an item to a list), the default value is in effect modified. This is generally not what was intended. A way around this is to use None as the default, and explicitly test for it in the body of the function,e.g.:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
"
"def"是Python中的可執(zhí)行語句,默認(rèn)參數(shù)在"def"的語句環(huán)境里被計(jì)算。如果你執(zhí)行了"def"語句多次,每次它都將會(huì)創(chuàng)建一個(gè)新的函數(shù)對(duì)象。接下來我們將看到例子。
用什么來代替?
像其他人所提到的那樣,用一個(gè)占位符來替代可以修改的默認(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']
譯者注:太水,真的不知道怎么翻譯了。我說下我的理解 有時(shí)邏輯上可能需要傳遞一個(gè)None,而你的默認(rèn)值可能又不是None,而且還剛好是個(gè)列表,列表不
可以寫在默認(rèn)值位置,所以你需要占位符,但是用None,你又不知道是不是調(diào)用者傳遞過來的那個(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語句時(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
<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}
這樣你可以訪問默認(rèn)參數(shù),你甚至可以修改它。
>>> function.func_defaults[0][:] = []
>>> function()
[1]
>>> function.func_defaults
([1],)
然而我不推薦你平時(shí)這么使用。
另一個(gè)重置默認(rèn)參數(shù)的方法是重新執(zhí)行相同的def語句,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.
- Python函數(shù)默認(rèn)參數(shù)設(shè)置的具體方法
- python函數(shù)默認(rèn)參數(shù)使用避坑指南
- Python如何定義有默認(rèn)參數(shù)的函數(shù)
- Python新手學(xué)習(xí)函數(shù)默認(rèn)參數(shù)設(shè)置
- python默認(rèn)參數(shù)調(diào)用方法解析
- python 默認(rèn)參數(shù)相關(guān)知識(shí)詳解
- Python中的默認(rèn)參數(shù)實(shí)例分析
- Python進(jìn)階-函數(shù)默認(rèn)參數(shù)(詳解)
- python 默認(rèn)參數(shù)問題的陷阱
- Python默認(rèn)參數(shù)的使用機(jī)制
相關(guān)文章
Keras設(shè)置以及獲取權(quán)重的實(shí)現(xiàn)
這篇文章主要介紹了Keras設(shè)置以及獲取權(quán)重的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-06-06詳解Python中4種超參自動(dòng)優(yōu)化算法的實(shí)現(xiàn)
要想模型效果好,每個(gè)算法工程師都應(yīng)該了解的流行超參數(shù)調(diào)優(yōu)技術(shù)。今天給大家總結(jié)超參自動(dòng)優(yōu)化方法:網(wǎng)格搜索、隨機(jī)搜索、貝葉斯優(yōu)化?和?Hyperband,感興趣的可以了解一下2022-05-05python實(shí)現(xiàn)各進(jìn)制轉(zhuǎn)換的總結(jié)大全
這篇文章主要給大家總結(jié)了python實(shí)現(xiàn)各進(jìn)制轉(zhuǎn)換的相關(guān)資料,其中包括字符串與十六進(jìn)制轉(zhuǎn)換、內(nèi)置函數(shù)hex()與進(jìn)制互轉(zhuǎn)等相關(guān)內(nèi)容,需要的朋友可以參考借鑒,下面來一起看看吧。2017-06-06python?os.stat()如何獲取相關(guān)文件的系統(tǒng)狀態(tài)信息
這篇文章主要介紹了python?os.stat()如何獲取相關(guān)文件的系統(tǒng)狀態(tài)信息,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11用Python的Flask框架結(jié)合MySQL寫一個(gè)內(nèi)存監(jiān)控程序
這篇文章主要介紹了用Python的Flask框架結(jié)合MySQL些一個(gè)內(nèi)存監(jiān)控程序的例子,并且能將結(jié)果作簡單的圖形化顯示,需要的朋友可以參考下2015-11-11Centos7下源碼安裝Python3 及shell 腳本自動(dòng)安裝Python3的教程
這篇文章主要介紹了Centos7下源碼安裝Python3 shell 腳本自動(dòng)安裝Python3的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03