Python函數(shù)的默認(rèn)參數(shù)設(shè)計示例詳解
在Python教程里,針對默認(rèn)參數(shù),給了一個“重要警告”的例子:
def f(a, L=[]): L.append(a) return L print(f(1)) print(f(2)) print(f(3))
默認(rèn)值只會執(zhí)行一次,也沒說原因。會打印出結(jié)果:
[1]
[1, 2]
[1, 2, 3]
因為學(xué)的第一門語言是Ruby,所以感覺有些奇怪。 但肯定的是方法f一定儲存了變量L。
準(zhǔn)備知識:指針
p指向不可變對象,比如數(shù)字。則相當(dāng)于p指針指向了不同的內(nèi)存地址。
p指向的是可變對象,比如list。list自身的改變,并不會改變list對象自身所在的內(nèi)存地址。所以p指向的內(nèi)存地址不變。
>>> p = 1 >>> id(p) >>> p = p + 1 >>> id(p) >>> p = 11 >>> id(p) >>> p = [] >>> id(p) >>> p.append(11) >>> id(p)
根本原因
Python函數(shù)的參數(shù)默認(rèn)值,是在編譯階段就綁定了。(寫代碼時就定義了。)
下面是一段從Python Common Gotchas中摘錄的原因解釋:
Python's default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.
由此可知:
- 在運行代碼時,運行到函數(shù)定義時,默認(rèn)參數(shù)的表達(dá)式就被執(zhí)行了。
- 函數(shù)調(diào)用時,不會再次運行默認(rèn)參數(shù)的表達(dá)式。⚠️ 這點和Ruby完全不同。
- 由此可知,如果默認(rèn)參數(shù),指向一個不變對象,例如L = 1。那么在函數(shù)調(diào)用時,在函數(shù)體內(nèi)對L重新賦值,L其實是一個新的指針, 指向的是一個新的內(nèi)存地址。而原來默認(rèn)參數(shù)L本身及指向的內(nèi)存地址,已經(jīng)儲存在最開始編譯時的函數(shù)定義中。可以用__default__查看。
- 如果默認(rèn)參數(shù)指向的是一個可變對象,如list, 那么L.append(a)是對可變對象自身的修改,L指向的內(nèi)存地址不變。所以每次調(diào)用函數(shù),默認(rèn)參數(shù)取出的都是這個內(nèi)存地址的對象。
第三條,修改上面的例子:
def f(a, L = 1): L = a print(id(L)) return L print("self",id(f.__defaults__[0])) print(f(1)) print("self",id(f.__defaults__[0])) print(f(33)) print("self",id(f.__defaults__[0])) #運行結(jié)果: self 4353170064 1 self 4353170064 33 self 4353170064
默認(rèn)參數(shù)L,在編譯階段就綁定了,儲存在__default__內(nèi)。函數(shù)體內(nèi)的L = a表達(dá)式,生成的是新的變量。返回的L是新的變量,和默認(rèn)參數(shù)無關(guān)。
第四條,還是上面的例子, 改一下默認(rèn)參數(shù)的類型為可變對象list:
def f(a, L = []): L.append(a) print(id(L)) return L # L = f(1) print("self",id(f.__defaults__[0])) print(f(1)) print("self",id(f.__defaults__[0])) print(f(33)) print("self",id(f.__defaults__[0]))
#返回結(jié)果 self 4337586048 [1] self 4337586048 [1, 33] self 4337586048
由id號可知,返回的是默認(rèn)參數(shù)自身。
如何避免這個陷阱帶來不必要麻煩
def f(a, L = None): if L is None: L = [] L.append(a) return L
為什么Python要這么設(shè)計
StackOverflow 上爭論很多。
Ruby之所以沒有這個問題,我想是因為Ruby的def關(guān)鍵字定義了一個封閉作用域,任何數(shù)據(jù)都必須通過參數(shù)傳入到方法內(nèi),才能用。
和Ruby比,Python參數(shù)的作用被大大消弱了。Python的出現(xiàn)晚于Ruby,其創(chuàng)始人肯定參考了Ruby的設(shè)計。拋棄了這個設(shè)計,選擇了類似javascript的函數(shù)方式。def定義的函數(shù),執(zhí)行時是可以接收外部作用域的變量的。
有觀點認(rèn)為:
出于Python編譯器的實現(xiàn)方式考慮,函數(shù)是一個內(nèi)部一級對象。而參數(shù)默認(rèn)值是這個對象的屬性。在其他任何語言中,對象屬性都是在對象創(chuàng)建時做綁定的。因此,函數(shù)參數(shù)默認(rèn)值在編譯時綁定也就不足為奇了。
本文參考了:http://cenalulu.github.io/python/default-mutable-arguments/#toc1 ,并加入了自己的理解。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
- python函數(shù)默認(rèn)參數(shù)使用避坑指南
- python函數(shù)的默認(rèn)參數(shù)請勿定義可變類型詳解
- Python中的函數(shù)參數(shù)(位置參數(shù)、默認(rèn)參數(shù)、可變參數(shù))
- Python如何定義有默認(rèn)參數(shù)的函數(shù)
- Python新手學(xué)習(xí)函數(shù)默認(rèn)參數(shù)設(shè)置
- Python函數(shù)默認(rèn)參數(shù)常見問題及解決方案
- Python中函數(shù)及默認(rèn)參數(shù)的定義與調(diào)用操作實例分析
- Python進(jìn)階-函數(shù)默認(rèn)參數(shù)(詳解)
- 深入講解Python函數(shù)中參數(shù)的使用及默認(rèn)參數(shù)的陷阱
- 詳細(xì)介紹Python函數(shù)中的默認(rèn)參數(shù)
- Python函數(shù)默認(rèn)參數(shù)設(shè)置的具體方法
相關(guān)文章
PyQt實現(xiàn)異步數(shù)據(jù)庫請求的實戰(zhàn)記錄
開發(fā)軟件的時候不可避免要和數(shù)據(jù)庫發(fā)生交互,但是有些 SQL 請求非常耗時,如果在主線程中發(fā)送請求,可能會造成界面卡頓,本文將介紹一種讓數(shù)據(jù)庫請求變得和前端的 ajax 請求一樣簡單,希望對大家有所幫助2023-12-12在Python中使用AOP實現(xiàn)Redis緩存示例
本篇文章主要介紹了在Python中使用AOP實現(xiàn)Redis緩存示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07Python Selenium實現(xiàn)無可視化界面過程解析
這篇文章主要介紹了Python Selenium實現(xiàn)無可視化界面過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08關(guān)于Python中compile() 函數(shù)簡單實用示例詳解
這篇文章主要介紹了關(guān)于compile() 函數(shù)簡單實用示例,compile() 函數(shù)將一個字符串編譯為字節(jié)代碼,compile將代碼編譯為代碼對象,應(yīng)用在代碼中可以提高效率,本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05如何使用Django默認(rèn)的Auth權(quán)限管理系統(tǒng)
本文主要介紹了如何使用Django默認(rèn)的Auth權(quán)限管理系統(tǒng),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02Opencv中cv2.cvtColor彩色圖轉(zhuǎn)灰度圖的其他6種方法
本文主要介紹了Opencv中cv2.cvtColor彩色圖轉(zhuǎn)灰度圖的其他6種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05