Python參數傳遞,既不是傳值也不是傳引用
來自專欄 Python私房菜
面試的時候,有沒有被問到Python傳參是傳引用還是傳值這種問題?有沒有聽到過Python傳參既不是傳值也不是傳引用這種說法?一個小小的參數默認值也可能讓代碼出現難以查找的bug?
如果你也遇到過上面的問題,不妨我們來探究下Python函數傳遞的種種。
萬物皆對象
Python中有一個非常重要的概念——萬物皆對象,無論是一個數字、字元串,還是數組、字典,在Python中都會以一個對象的形式存在。
a = 123
對於上面這行代碼,在Python看來就是創建一個PyObject對象,值為123,然後定義一個指針a,a指向這個PyObject對象。
可變對象和不可變對象
Python中的對象分為兩種類型,可變對象和不可變對象,不可變對象指tuple、str、int等類型的對象,可變對象指的是dict、list、自定義對象等類型的對象,我們用一段代碼說明他們的區別。
a = [1, 2, 3]print(id(a)) # 2587116690248a += [4]print(id(a)) # 2587116690248b = 1print(id(b)) # 2006430784b += 1print(id(b)) # 2006430816
上面代碼中我們分別定義了一個可變對象和一個不可變對象,並且對他們進行修改,列印修改前後的對象標識可以發現,對可變對象進行修改,變數對其引用不會發生變化,對不可變對象進行修改,變數引用發生了變化。
上圖是一個可變對象,當修改對象時,例如刪除數組中的一個元素,實際上把其中一個元素從對象中移除,對象本身的標識是不發生變化的。
改變一個不可變對象時,例如給一個int型加2,語法上看上去是直接修改了i這個對象,但是如前面所說,i只是一個指向對象73的一個變數,Python會將這個變數指向的對象加2後,生成一個新的對象,然後再讓i指向這個新的對象。
參數傳遞時的表現
了解了對象的原理後,我們就可以來嘗試理解一下參數傳遞時他們的不同表現了。
a = [1, 2, 3]print(id(a)) # 1437494204232def mutable(a): print(id(a)) # 1437494204232 a += [4] print(id(a)) # 1437494204232mutable(a)b = 1print(id(b)) # 2006430784def immutable(b): print(id(b)) # 2006430784 b += 1 print(id(b)) # 2006430816immutable(b)
通過上面的代碼可以看出,修改傳進的可變參數時,會對外部對象產生影響,修改不可變參數時則不會影響。
概括地說,Python參數傳遞時,既不是傳對象也不是傳引用,之所以會有上述的區別,跟Python的對象機制有關,參數傳遞只是給對象綁定了一個新的變數(實際上是傳遞C中的指針)。
參數傳遞時的坑
理解了參數傳遞的邏輯,我們需要注意一下這種邏輯可能引發的問題。
def test(b=[]): b += [1] print(b)test() # [1]test() # [1, 1]test() # [1, 1, 1]
上面的代碼的輸出,按照可變對象傳參的邏輯,應該每次調用都輸出[1]才對,而實際輸出看上去好像默認參數好像只生效了一次。原因在於Python的函數也是對象(萬物皆對象),這個對象只初始化一次,加上參數又是不可變對象,所以每次調用實際上都修改的是一個對象。
解決這個問題,推薦再參數傳遞可變對象時,默認值設置為None,在函數內部對None進行判斷後再賦予默認值。
def test(b=None): b = b or [] b += [1] print(b)test() # [1]test() # [1]test() # [1]
再看一段代碼。
i = 1def test(a=i): print(a)i = 2test() # 1
由於參數默認值是在函數定義時而不是函數執行時確定的,所以這段代碼test方法的參數默認值時1而不是2。
關注Python私房菜
微信搜索公眾號【Python私房菜】加關注
推薦閱讀:
※原生Python寫parser
※Python基礎知識篇
※Python 中 Requests 庫的用法
※給大家看點好玩的,用Python畫牛頓分形
TAG:Python |