电竞比分网-中国电竞赛事及体育赛事平台

分享

三、Python函數(shù)參數(shù)傳遞機(jī)制(超級(jí)詳細(xì))

 星光閃亮圖書館 2019-08-08
Python中,函數(shù)參數(shù)由實(shí)參傳遞給形參的過(guò)程,是由參數(shù)傳遞機(jī)制來(lái)控制的。通過(guò)學(xué)習(xí)《Python函數(shù)值傳遞和引用傳遞》一節(jié)我們知道,根據(jù)實(shí)際參數(shù)的類型不同,函數(shù)參數(shù)的傳遞方式分為值傳遞和引用傳遞(又稱為地址傳遞),本節(jié)將對(duì)這兩種傳遞機(jī)制做深度剖析。

Python函數(shù)參數(shù)的值傳遞機(jī)制

所謂值傳遞,實(shí)際上就是將實(shí)際參數(shù)值的副本(復(fù)制品)傳入函數(shù),而參數(shù)本身不會(huì)受到任何影響。

值傳遞的方式,類似于《西游記》里的孫悟空,它復(fù)制一個(gè)假孫悟空,假孫悟空具有的能力和真孫悟空相同,可除妖或被砍頭。但不管這個(gè)假孫悟空遇到什么事,真孫悟空都不會(huì)受到任何影響。與此類似,傳入函數(shù)的是實(shí)際參數(shù)值的復(fù)制品,不管在函數(shù)中對(duì)這個(gè)復(fù)制品如何操作,實(shí)際參數(shù)值本身不會(huì)受到任何影響。

下面程序演示了函數(shù)參數(shù)進(jìn)行值傳遞的效果:
  1. def swap(a , b) :
  2. # 下面代碼實(shí)現(xiàn)a、b變量的值交換
  3. a, b = b, a
  4. print("swap函數(shù)里,a的值是", \
  5. a, ";b的值是", b)
  6. a = 6
  7. b = 9
  8. swap(a , b)
  9. print("交換結(jié)束后,變量a的值是", \
  10. a , ";變量b的值是", b)
運(yùn)行上面程序,將看到如下運(yùn)行結(jié)果:

swap函數(shù)里,a的值是 9 ;b的值是 6
交換結(jié)束后,變量a的值是 6 ;變量b的值是 9

從上面的運(yùn)行結(jié)果來(lái)看,在 swap() 函數(shù)里,a 和 b 的值分別是 9、6,交換結(jié)束后,變量 a 和 b 的值依然是 6、9。從這個(gè)運(yùn)行結(jié)果可以看出,程序中實(shí)際定義的變量 a 和 b,并不是 swap() 函數(shù)里的 a 和 b 。

正如前面所講的,swap() 函數(shù)里的 a 和 b 只是主程序中變量 a 和 b 的復(fù)制品。下面通過(guò)示意圖來(lái)說(shuō)明上面程序的執(zhí)行過(guò)程。

上面程序開始定義了 a、b 兩個(gè)局部變量,這兩個(gè)變量在內(nèi)存中的存儲(chǔ)示意圖如圖 1 所示。

主棧區(qū)中 a、b 變量存儲(chǔ)示意圖
圖 1 主棧區(qū)中 a、b 變量存儲(chǔ)示意圖

當(dāng)程序執(zhí)行 swap() 函數(shù)時(shí),系統(tǒng)進(jìn)入 swap() 函數(shù),并將主程序中的 a、b 變量作為參數(shù)值傳入 swap() 函數(shù),但傳入 swap() 函數(shù)的只是 a、b 的副本,而不是 a、b 本身。進(jìn)入 swap() 函數(shù)后,系統(tǒng)中產(chǎn)生了 4 個(gè)變量,這 4 個(gè)變量在內(nèi)存中的存儲(chǔ)示意圖如圖 2 所示。

主棧區(qū)的變量作為參數(shù)值傳入swap()函數(shù)后存儲(chǔ)示意圖
圖 2 主棧區(qū)的變量作為參數(shù)值傳入 swap() 函數(shù)后存儲(chǔ)示意圖

當(dāng)在主程序中調(diào)用 swap() 函數(shù)時(shí),系統(tǒng)分別為主程序和 swap() 函數(shù)分配兩塊棧區(qū),用于保存它們的局部變量。將主程序中的 a、b 變量作為參數(shù)值傳入 swap() 函數(shù),實(shí)際上是在 swap() 函數(shù)棧區(qū)中重新產(chǎn)生了兩個(gè)變量 a、b,并將主程序棧區(qū)中 a、b 變量的值分別賦值給 swap() 函數(shù)棧區(qū)中的 a、b 參數(shù)(就是對(duì) swap() 函數(shù)的 a、b 兩個(gè)變量進(jìn)行初始化)。此時(shí),系統(tǒng)存在兩個(gè) a 變量、兩個(gè) b 變量,只是存在于不同的棧區(qū)中而己。

程序在 swap() 函數(shù)中交換 a、b 兩個(gè)變量的值,實(shí)際上是對(duì)圖 2 中灰色區(qū)域的 a、b 變量進(jìn)行交換。交換結(jié)束后,輸出 swap() 函數(shù)中 a、b 變量的值,可以看到 a 的值為 9,b 的值為 6,此時(shí)在內(nèi)存中的存儲(chǔ)示意圖如圖 3 所示。

swap()函數(shù)中a、b 交換之后的存儲(chǔ)示意圖
圖 3 swap() 函數(shù)中 a、b 交換之后的存儲(chǔ)示意圖

對(duì)比圖 3 與圖 1,可以看到兩個(gè)示意圖中主程序棧區(qū)中 a、b 的值并未有任何改變,程序改變的只是 swap() 函數(shù)棧區(qū)中 a、b 的值。這就是值傳遞的實(shí)質(zhì):當(dāng)系統(tǒng)開始執(zhí)行函數(shù)時(shí),系統(tǒng)對(duì)形參執(zhí)行初始化,就是把實(shí)參變量的值賦給函數(shù)的形參變量,在函數(shù)中操作的并不是實(shí)際的實(shí)參變量。

Python函數(shù)參數(shù)的引用傳遞

如果實(shí)際參數(shù)的數(shù)據(jù)類型是可變對(duì)象(列表、字典),則函數(shù)參數(shù)的傳遞方式將采用引用傳遞方式。需要注意的是,引用傳遞方式的底層實(shí)現(xiàn),采用的依然還是值傳遞的方式。

下面程序示范了引用傳遞參數(shù)的效果:
  1. def swap(dw):
  2. # 下面代碼實(shí)現(xiàn)dw的a、b兩個(gè)元素的值交換
  3. dw['a'], dw['b'] = dw['b'], dw['a']
  4. print("swap函數(shù)里,a元素的值是",\
  5. dw['a'], ";b元素的值是", dw['b'])
  6. dw = {'a': 6, 'b': 9}
  7. swap(dw)
  8. print("交換結(jié)束后,a元素的值是",\
  9. dw['a'], ";b元素的值是", dw['b'])
運(yùn)行上面程序,將看到如下運(yùn)行結(jié)果:

swap函數(shù)里,a元素的值是 9 ;b元素的值是 6
交換結(jié)束后,a元素的值是 9 ;b元素的值是 6

從上面的運(yùn)行結(jié)果來(lái)看,在 swap() 函數(shù)里,dw 字典的 a、b 兩個(gè)元素的值被交換成功。不僅如此,當(dāng) swap() 函數(shù)執(zhí)行結(jié)束后,主程序中 dw 字典的 a、b 兩個(gè)元素的值也被交換了。這很容易造成一種錯(cuò)覺,即在調(diào)用 swap() 函數(shù)時(shí),傳入 swap() 函數(shù)的就是 dw 字典本身,而不是它的復(fù)制品。但這只是一種錯(cuò)覺,下面還是結(jié)合示意圖來(lái)說(shuō)明程序的執(zhí)行過(guò)程。

程序開始創(chuàng)建了一個(gè)字典對(duì)象,并定義了一個(gè) dw 引用變量(其實(shí)就是一個(gè)指針)指向字典對(duì)象,這意味著此時(shí)內(nèi)存中有兩個(gè)東西:對(duì)象本身和指向該對(duì)象的引用變量。此時(shí)在系統(tǒng)內(nèi)存中的存儲(chǔ)示意圖如圖 4 所示:

主程序創(chuàng)建了字典對(duì)象后存儲(chǔ)示意圖
圖 4 主程序創(chuàng)建了字典對(duì)象后存儲(chǔ)示意圖

接下來(lái)主程序開始調(diào)用 swap() 函數(shù),在調(diào)用 swap() 函數(shù)時(shí),dw 變量作為參數(shù)傳入 swap() 函數(shù),這里依然采用值傳遞方式:把主程序中 dw 變量的值賦給 swap() 函數(shù)的 dw 形參,從而完成 swap() 函數(shù)的 dw 參數(shù)的初始化。值得指出的是,主程序中的 dw 是一個(gè)引用變量(也就是一個(gè)指針),它保存了字典對(duì)象的地址值,當(dāng)把 dw 的值賦給 swap() 函數(shù)的 dw 參數(shù)后,就是讓 swap() 函數(shù)的 dw 參數(shù)也保存這個(gè)地址值,即也會(huì)引用到同一個(gè)字典對(duì)象。圖 5 顯示了 dw 字典傳入 swap() 函數(shù)后的存儲(chǔ)示意圖。

dw字典傳入swap()函數(shù)后存儲(chǔ)示意圖
圖 5 dw 字典傳入 swap() 函數(shù)后存儲(chǔ)示意圖

從圖 5 來(lái)看,這種參數(shù)傳遞方式是不折不扣的值傳遞方式,系統(tǒng)一樣復(fù)制了dw 的副本傳入 swap() 函數(shù)。但由于 dw 只是一個(gè)引用變量,因此系統(tǒng)復(fù)制的是 dw 變量,并未復(fù)制字典本身。

當(dāng)程序在 swap() 函數(shù)中操作 dw 參數(shù)時(shí),由于 dw 只是一個(gè)引用變量,故實(shí)際操作的還是字典對(duì)象。此時(shí),不管是操作主程序中的 dw 變量,還是操作 swap() 函數(shù)里的 dw 參數(shù),其實(shí)操作的都是它們共同引用的字典對(duì)象,它們引用的是同一個(gè)字典對(duì)象。因此,當(dāng)在 swap() 函數(shù)中交換 dw 參數(shù)所引用字典對(duì)象的 a、b 兩個(gè)元素的值后,可以看到在主程序中 dw 變量所引用字典對(duì)象的 a、b 兩個(gè)元素的值也被交換了。

為了更好地證明主程序中的 dw 和 swap() 函數(shù)中的 dw 是兩個(gè)變量,在 swap() 函數(shù)的最后一行增加如下代碼:

#把dw 直接賦值為None,讓它不再指向任何對(duì)象
dw = None

運(yùn)行上面代碼,結(jié)果是 swap() 函數(shù)中的 dw 變量不再指向任何對(duì)象,程序其他地方?jīng)]有任何改變。主程序調(diào)用 swap() 函數(shù)后,再次訪問 dw 變量的 a、b 兩個(gè)元素,依然可以輸出 9、6??梢?,主程序中的 dw 變量沒有受到任何影響。實(shí)際上,當(dāng)在 swap() 函數(shù)中增加“dw =None”代碼后,在內(nèi)存中的存儲(chǔ)示意圖如圖 6 所示。

將swap()函數(shù)中的dw賦值為None 后存儲(chǔ)示意圖
圖 6 將 swap() 函數(shù)中的 dw 賦值為 None 后存儲(chǔ)示意圖

從圖 6 來(lái)看,把 swap() 函數(shù)中的 dw 賦值為 None 后,在 swap() 函數(shù)中失去了對(duì)字典對(duì)象的引用,不可再訪問該字典對(duì)象。但主程序中的 dw 變量不受任何影響,依然可以引用該字典對(duì)象,所以依然可以輸出字典對(duì)象的 a、b 元素的值。

通過(guò)上面介紹可以得出如下兩個(gè)結(jié)論:
  1. 不管什么類型的參數(shù),在 Python 函數(shù)中對(duì)參數(shù)直接使用“=”符號(hào)賦值是沒用的,直接使用“=”符號(hào)賦值并不能改變參數(shù)。
  2. 如果需要讓函數(shù)修改某些數(shù)據(jù),則可以通過(guò)把這些數(shù)據(jù)包裝成列表、字典等可變對(duì)象,然后把列表、字典等可變對(duì)象作為參數(shù)傳入函數(shù),在函數(shù)中通過(guò)列表、字典的方法修改它們,這樣才能改變這些數(shù)據(jù)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多