Python中,函數參數由實參傳遞給形參的過程,是由參數傳遞機制來控制的。經過學習《Python函數值傳遞和引用傳遞》一節咱們知道,根據實際參數的類型不一樣,函數參數的傳遞方式分爲值傳遞和引用傳遞(又稱爲地址傳遞),本節將對這兩種傳遞機制作深度剖析。python
所謂值傳遞,實際上就是將實際參數值的副本(複製品)傳入函數,而參數自己不會受到任何影響。函數
值傳遞的方式,相似於《西遊記》裏的孫悟空,它複製一個假孫悟空,假孫悟空具備的能力和真孫悟空相同,可除妖或被砍頭。但無論這個假孫悟空遇到什麼事,真孫悟空都不會受到任何影響。與此相似,傳入函數的是實際參數值的複製品,無論在函數中對這個複製品如何操做,實際參數值自己不會受到任何影響。學習
下面程序演示了函數參數進行值傳遞的效果:spa
def swap(a , b) : # 下面代碼實現a、b變量的值交換 a, b = b, a print("swap函數裏,a的值是", \ a, ";b的值是", b) a = 6 b = 9 swap(a , b) print("交換結束後,變量a的值是", \ a , ";變量b的值是", b)
運行上面程序,將看到以下運行結果:指針
swap函數裏,a的值是 9 ;b的值是 6 交換結束後,變量a的值是 6 ;變量b的值是 9
從上面的運行結果來看,在 swap() 函數裏,a 和 b 的值分別是 九、6,交換結束後,變量 a 和 b 的值依然是 六、9。從這個運行結果能夠看出,程序中實際定義的變量 a 和 b,並非 swap() 函數裏的 a 和 b 。
正如前面所講的,swap() 函數裏的 a 和 b 只是主程序中變量 a 和 b 的複製品。下面經過示意圖來講明上面程序的執行過程。
上面程序開始定義了 a、b 兩個局部變量,這兩個變量在內存中的存儲示意圖如圖 1 所示。code
當程序執行 swap() 函數時,系統進入 swap() 函數,並將主程序中的 a、b 變量做爲參數值傳入 swap() 函數,但傳入 swap() 函數的只是 a、b 的副本,而不是 a、b 自己。進入 swap() 函數後,系統中產生了 4 個變量,這 4 個變量在內存中的存儲示意圖如圖 2 所示。對象
當在主程序中調用 swap() 函數時,系統分別爲主程序和 swap() 函數分配兩塊棧區,用於保存它們的局部變量。將主程序中的 a、b 變量做爲參數值傳入 swap() 函數,其實是在 swap() 函數棧區中從新產生了兩個變量 a、b,並將主程序棧區中 a、b 變量的值分別賦值給 swap() 函數棧區中的 a、b 參數(就是對 swap() 函數的 a、b 兩個變量進行初始化)。此時,系統存在兩個 a 變量、兩個 b 變量,只是存在於不一樣的棧區中而己。
程序在 swap() 函數中交換 a、b 兩個變量的值,其實是對圖 2 中灰色區域的 a、b 變量進行交換。交換結束後,輸出 swap() 函數中 a、b 變量的值,能夠看到 a 的值爲 9,b 的值爲 6,此時在內存中的存儲示意圖如圖 3 所示。blog
對比圖 3 與圖 1,能夠看到兩個示意圖中主程序棧區中 a、b 的值並未有任何改變,程序改變的只是 swap() 函數棧區中 a、b 的值。這就是值傳遞的實質:當系統開始執行函數時,系統對形參執行初始化,就是把實參變量的值賦給函數的形參變量,在函數中操做的並非實際的實參變量。內存
若是實際參數的數據類型是可變對象(列表、字典),則函數參數的傳遞方式將採用引用傳遞方式。須要注意的是,引用傳遞方式的底層實現,採用的依然仍是值傳遞的方式。
下面程序示範了引用傳遞參數的效果:class
def swap(dw): # 下面代碼實現dw的a、b兩個元素的值交換 dw['a'], dw['b'] = dw['b'], dw['a'] print("swap函數裏,a元素的值是",\ dw['a'], ";b元素的值是", dw['b']) dw = {'a': 6, 'b': 9} swap(dw) print("交換結束後,a元素的值是",\ dw['a'], ";b元素的值是", dw['b'])
運行上面程序,將看到以下運行結果:
swap函數裏,a元素的值是 9 ;b元素的值是 6 交換結束後,a元素的值是 9 ;b元素的值是 6
從上面的運行結果來看,在 swap() 函數裏,dw 字典的 a、b 兩個元素的值被交換成功。不只如此,當 swap() 函數執行結束後,主程序中 dw 字典的 a、b 兩個元素的值也被交換了。這很容易形成一種錯覺,即在調用 swap() 函數時,傳入 swap() 函數的就是 dw 字典自己,而不是它的複製品。但這只是一種錯覺,下面仍是結合示意圖來講明程序的執行過程。
程序開始建立了一個字典對象,並定義了一個 dw 引用變量(其實就是一個指針)指向字典對象,這意味着此時內存中有兩個東西:對象自己和指向該對象的引用變量。此時在系統內存中的存儲示意圖如圖 4 所示:
接下來主程序開始調用 swap() 函數,在調用 swap() 函數時,dw 變量做爲參數傳入 swap() 函數,這裏依然採用值傳遞方式:把主程序中 dw 變量的值賦給 swap() 函數的 dw 形參,從而完成 swap() 函數的 dw 參數的初始化。值得指出的是,主程序中的 dw 是一個引用變量(也就是一個指針),它保存了字典對象的地址值,當把 dw 的值賦給 swap() 函數的 dw 參數後,就是讓 swap() 函數的 dw 參數也保存這個地址值,即也會引用到同一個字典對象。圖 5 顯示了 dw 字典傳入 swap() 函數後的存儲示意圖。
從圖 5 來看,這種參數傳遞方式是徹徹底底的值傳遞方式,系統同樣複製了dw 的副本傳入 swap() 函數。但因爲 dw 只是一個引用變量,所以系統複製的是 dw 變量,並未複製字典自己。
當程序在 swap() 函數中操做 dw 參數時,因爲 dw 只是一個引用變量,故實際操做的仍是字典對象。此時,無論是操做主程序中的 dw 變量,仍是操做 swap() 函數裏的 dw 參數,其實操做的都是它們共同引用的字典對象,它們引用的是同一個字典對象。所以,當在 swap() 函數中交換 dw 參數所引用字典對象的 a、b 兩個元素的值後,能夠看到在主程序中 dw 變量所引用字典對象的 a、b 兩個元素的值也被交換了。
爲了更好地證實主程序中的 dw 和 swap() 函數中的 dw 是兩個變量,在 swap() 函數的最後一行增長以下代碼:
#把dw 直接賦值爲None,讓它再也不指向任何對象 dw = None
運行上面代碼,結果是 swap() 函數中的 dw 變量再也不指向任何對象,程序其餘地方沒有任何改變。主程序調用 swap() 函數後,再次訪問 dw 變量的 a、b 兩個元素,依然能夠輸出 九、6。可見,主程序中的 dw 變量沒有受到任何影響。實際上,當在 swap() 函數中增長「dw =None」代碼後,在內存中的存儲示意圖如圖 6 所示。
從圖 6 來看,把 swap() 函數中的 dw 賦值爲 None 後,在 swap() 函數中失去了對字典對象的引用,不可再訪問該字典對象。但主程序中的 dw 變量不受任何影響,依然能夠引用該字典對象,因此依然能夠輸出字典對象的 a、b 元素的值。
經過上面介紹能夠得出以下兩個結論: