6 回答

TA貢獻(xiàn)1801條經(jīng)驗(yàn) 獲得超16個(gè)贊
實(shí)際上,這不是設(shè)計(jì)缺陷,并不是因?yàn)閮?nèi)部或性能。
它只是因?yàn)镻ython中的函數(shù)是第一類對象,而不僅僅是一段代碼。
一旦你以這種方式思考,那么它就完全有意義了:一個(gè)函數(shù)是一個(gè)被定義的對象; 默認(rèn)參數(shù)是一種“成員數(shù)據(jù)”,因此它們的狀態(tài)可能會(huì)從一個(gè)調(diào)用更改為另一個(gè)調(diào)用 - 與任何其他對象完全相同。
無論如何,Effbot 對Python中的默認(rèn)參數(shù)值中出現(xiàn)這種行為的原因有一個(gè)非常好的解釋。
我發(fā)現(xiàn)它非常清楚,我真的建議閱讀它以更好地了解函數(shù)對象的工作原理。

TA貢獻(xiàn)2016條經(jīng)驗(yàn) 獲得超9個(gè)贊
假設(shè)您有以下代碼
fruits = ("apples", "bananas", "loganberries")
def eat(food=fruits):
...
當(dāng)我看到吃的聲明時(shí),最令人驚訝的是認(rèn)為如果沒有給出第一個(gè)參數(shù),它將等于元組 ("apples", "bananas", "loganberries")
但是,假設(shè)后面的代碼,我會(huì)做類似的事情
def some_random_function():
global fruits
fruits = ("blueberries", "mangos")
然后,如果默認(rèn)參數(shù)在函數(shù)執(zhí)行而不是函數(shù)聲明中被綁定,那么我會(huì)驚訝地發(fā)現(xiàn)水果已被改變(以非常糟糕的方式)。這比發(fā)現(xiàn)foo上面的函數(shù)改變列表更令人驚訝的IMO 。
真正的問題在于可變變量,并且所有語言在某種程度上都存在這個(gè)問題。這是一個(gè)問題:假設(shè)在Java中我有以下代碼:
StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) ); // does this work?
現(xiàn)在,我的地圖StringBuffer在放入地圖時(shí)是否使用了鍵的值,還是通過引用存儲(chǔ)了鍵?無論哪種方式,有人感到驚訝; 嘗試將對象從Map使用中取出的值與他們放入的對象相同的人,或者即使他們使用的鍵實(shí)際上是同一個(gè)對象而無法檢索其對象的人用于將其放入映射的(這實(shí)際上是Python不允許其可變內(nèi)置數(shù)據(jù)類型用作字典鍵的原因)。
你的例子是一個(gè)很好的例子,Python新人會(huì)感到驚訝和被咬。但我認(rèn)為,如果我們“修復(fù)”這個(gè),那么這只會(huì)產(chǎn)生一種不同的情況,即他們會(huì)被咬傷,而那種情況甚至?xí)恢庇^。而且,在處理可變變量時(shí)總是如此; 你總是遇到一些情況,根據(jù)他們正在編寫的代碼,某人可能直觀地期望一種或相反的行為。
我個(gè)人喜歡Python當(dāng)前的方法:默認(rèn)函數(shù)參數(shù)在定義函數(shù)時(shí)進(jìn)行評估,并且該對象始終是默認(rèn)值。我想他們可以使用空列表進(jìn)行特殊情況,但這種特殊的外殼會(huì)引起更多的驚訝,更不用說倒退不兼容了

TA貢獻(xiàn)1811條經(jīng)驗(yàn) 獲得超6個(gè)贊
AFAICS尚未發(fā)布文檔的相關(guān)部分:
執(zhí)行函數(shù)定義時(shí),將評估默認(rèn)參數(shù)值。這意味著當(dāng)定義函數(shù)時(shí),表達(dá)式被計(jì)算一次,并且每次調(diào)用使用相同的“預(yù)先計(jì)算”值。這對于理解默認(rèn)參數(shù)是可變對象(例如列表或字典)時(shí)尤其重要:如果函數(shù)修改對象(例如,通過將項(xiàng)附加到列表),則默認(rèn)值實(shí)際上被修改。這通常不是預(yù)期的。解決這個(gè)問題的方法是使用None作為默認(rèn)值,并在函數(shù)體中顯式測試它[...]

TA貢獻(xiàn)1788條經(jīng)驗(yàn) 獲得超4個(gè)贊
我對Python解釋器內(nèi)部工作一無所知(我也不是編譯器和解釋器方面的專家)所以如果我提出任何不可知或不可能的建議,請不要責(zé)怪我。
如果python對象是可變的,我認(rèn)為在設(shè)計(jì)默認(rèn)參數(shù)時(shí)應(yīng)該考慮到這一點(diǎn)。實(shí)例化列表時(shí):
a = []
你希望得到一個(gè)新的列表引用a
。
為什么要a=[]
進(jìn)去
def x(a=[]):
在函數(shù)定義上實(shí)例化一個(gè)新列表而不是在調(diào)用上?就像你問“用戶是否提供參數(shù)然后實(shí)例化一個(gè)新列表并使用它就好像它是由調(diào)用者生成”一樣。我認(rèn)為這是模棱兩可的:
def x(a=datetime.datetime.now()):
用戶,是否要a
默認(rèn)為與定義或執(zhí)行時(shí)相對應(yīng)的日期時(shí)間x
?在這種情況下,與前一個(gè)一樣,我將保持相同的行為,就好像默認(rèn)參數(shù)“assignment”是函數(shù)的第一條指令(datetime.now()
在函數(shù)調(diào)用上調(diào)用)。另一方面,如果用戶想要定義時(shí)間映射,他可以寫:
b = datetime.datetime.now()def x(a=b):
我知道,我知道:這是一個(gè)封閉?;蛘撸琍ython可能會(huì)提供一個(gè)關(guān)鍵字來強(qiáng)制定義時(shí)綁定:
def x(static a=b):

TA貢獻(xiàn)1906條經(jīng)驗(yàn) 獲得超10個(gè)贊
嗯,原因很簡單,在執(zhí)行代碼時(shí)完成綁定,并且執(zhí)行函數(shù)定義,以及......定義函數(shù)時(shí)。
比較一下:
class BananaBunch: bananas = [] def addBanana(self, banana): self.bananas.append(banana)
此代碼遭受完全相同的意外事件。bananas是一個(gè)類屬性,因此,當(dāng)您向其添加內(nèi)容時(shí),它會(huì)添加到該類的所有實(shí)例中。原因完全一樣。
這只是“如何工作”,并且在功能案例中使其工作方式可能很復(fù)雜,并且在類的情況下可能不可能,或者至少減慢對象實(shí)例化的速度,因?yàn)槟惚仨毐3诸惔a并在創(chuàng)建對象時(shí)執(zhí)行它。
是的,這是出乎意料的。但是一旦下降了,它就完全適合Python的工作方式。事實(shí)上,它是一個(gè)很好的教學(xué)輔助工具,一旦你理解了為什么會(huì)這樣,你就會(huì)更好地理解python。
這說它應(yīng)該在任何優(yōu)秀的Python教程中占據(jù)突出地位。因?yàn)檎缒闼岬降模總€(gè)人遲早都會(huì)遇到這個(gè)問題。

TA貢獻(xiàn)1797條經(jīng)驗(yàn) 獲得超4個(gè)贊
我曾經(jīng)認(rèn)為在運(yùn)行時(shí)創(chuàng)建對象將是更好的方法。我現(xiàn)在不太確定,因?yàn)槟愦_實(shí)失去了一些有用的功能,盡管它可能是值得的,不管只是為了防止新手混淆。這樣做的缺點(diǎn)是:
1.表現(xiàn)
def foo(arg=something_expensive_to_compute())):
...
如果使用了調(diào)用時(shí)評估,則每次使用函數(shù)時(shí)都會(huì)調(diào)用昂貴的函數(shù)而不使用參數(shù)。您要么為每次調(diào)用付出昂貴的代價(jià),要么需要在外部手動(dòng)緩存該值,污染您的命名空間并添加詳細(xì)程度。
2.強(qiáng)制綁定參數(shù)
一個(gè)有用的技巧是在創(chuàng)建lambda時(shí)將lambda的參數(shù)綁定到變量的當(dāng)前綁定。例如:
funcs = [ lambda i=i: i for i in range(10)]
這將返回分別返回0,1,2,3 ...的函數(shù)列表。如果行為發(fā)生了變化,它們將綁定i到i 的調(diào)用時(shí)間值,因此您將獲得所有返回的函數(shù)列表9。
否則實(shí)現(xiàn)此方法的唯一方法是使用i綁定創(chuàng)建進(jìn)一步的閉包,即:
def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]
3.內(nèi)省
考慮一下代碼:
def foo(a='test', b=100, c=[]):
print a,b,c
我們可以使用inspect模塊獲取有關(guān)參數(shù)和默認(rèn)值的信息
>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))
這些信息對于文檔生成,元編程,裝飾器等非常有用。
現(xiàn)在,假設(shè)可以更改默認(rèn)值的行為,以便這相當(dāng)于:
_undefined = object() # sentinel value
def foo(a=_undefined, b=_undefined, c=_undefined)
if a is _undefined: a='test'
if b is _undefined: b=100
if c is _undefined: c=[]
但是,我們已經(jīng)失去了內(nèi)省的能力,并且看到了默認(rèn)參數(shù)是什么。因?yàn)闆]有構(gòu)造對象,所以我們不能在沒有實(shí)際調(diào)用函數(shù)的情況下獲取它們。我們能做的最好的事情是存儲(chǔ)源代碼并將其作為字符串返回。
添加回答
舉報(bào)