Python 到底是值傳遞還是引用傳遞


我們平時寫的 Python 程序中充斥着大量的函數,包括系統自帶函數和自定義函數,當我們調用函數時直接將參數傳遞進去然后坐等接收返回值即可,簡直不要太好用。那么你知道函數的參數是怎么傳遞的么,是值傳遞還是引用傳遞呢,什么又是值傳遞和引用傳遞呢?

這個問題對於很多初學者還是比較有難度的,看到這里你可以稍加停頓,自己思考一下,看看自己是否真正理解了。很多人只是知道概念但是讓他說他又說不清楚,思考過后如果你還覺得模糊的話,往下仔細看,我今天就帶着你深入剖析下函數的參數傳遞機制。

為了搞清楚函數的參數傳遞機制,你必須先徹底理解形參和實參。例如下面的 sayHello 函數,括號里面的 name 就是形參,而當調用函數時傳遞的 name 是實參。

def sayHello(name): # name 是形式參數
    print("Hello %s" % name)


name = "hanmeimei" # name 是實際參數
sayHello(name)

# 輸出結果
Hello hanmeimei

值傳遞 OR 引用傳遞
上面我們說了,當調用函數時我們會把實際參數傳遞給形式參數。而這個傳遞過程有兩種,就是我們上文說的值傳遞和引用傳遞了。

顧名思義,所謂值傳遞就是指在傳遞過程中將實際參數的值復制一份傳遞給形式參數,這樣即使在函數執行過程中對形式參數進行了修改,形式參數也不會有所改變,因為二者互不干擾。而引用傳遞是值將實際參數的引用傳遞給實際參數,這樣二者就會指向同一塊內存地址,在函數執行過程中對形式參數進行了修改,形式參數也會一並=被修改。

為了故事的順利發展,我們先來看看 Python 中關於變量的賦值。

>>> a = 10
>>> b = a
>>> a = a + 10
>>> a
20
>>> b
10
>>>

在上述的例子中,我們聲明了一個變量 a,其值為 10,然后將 b 也指向 a,這是在內存中的布局是這樣的,變量 a 和 b 會指向同一個對象 10,而不是給 b 重新生成一個新的對象。

由此可知,同一個對象是可以被多個對象引用的。

當執行完 a = a + 10 后,因為整數是不可變對象,所以並不會將 10 變成 20,而是生成一個新的對象 20 ,然后 a 會指向這個新的對象。b 還是指向舊對象 10。

所以,最后就是 a 為 20,而 b 為 10。

理解了上面的賦值過程之后,我們再來看看參數的傳遞。老規矩,還是直接看例子吧,代碼是不會騙人的。

def swap(a, b):
    a, b = b, a
    print("in swap a = %d and b = %d " % (a, b))


a = 100
b = 200
swap(a, b)
print("in main a = %d and b = %d " % (a, b))

## 輸出結果
in swap a = 200 and b = 100 
in main a = 100 and b = 200 

我們在函數 swap 中交換 a 和 b 的值,然后分別在主函數和 swap 函數中輸出其結果,由結果可知,swap 函數並不會改變實際參數 a,b 的值,因此我們可以得出結論,Python 函數參數是按照值傳遞的。

別急,不妨再看多一個例子。

def swap(list):
    list.append(4)
    print("in swap list is %s " % list)


list_x = [1, 2, 3]
swap(list_x)
print("in main list is %s " % list_x)

## 輸出結果
in swap list is [1, 2, 3, 4] 
in main list is [1, 2, 3, 4] 

咦,值被改了,這不就是引用傳遞了么。於是,我們又得出結論,Python 函數參數是按照引用傳遞的。

這未免有點太不嚴謹了,事實上我們上面的第二個例子有點不太嚴謹,我稍微修改了下 swap 函數,咱們在看看測試結果。

def swap(list):
    list = list + [4]
    print("in swap list is %s " % list)

## 輸出結果
in swap list is [1, 2, 3, 4] 
in main list is [1, 2, 3] 

我們只是更改了 swap 函數內一行代碼,結果就完全不一樣了。為了更好的理解其執行過程,我畫了張圖。

在第一個關於 list 的例子中,我們首先聲明了一個列表,其中的元素為 [1,2,3],此時其內存布局如上圖中的步驟一所示。list_x 指向內存地址為 OX7686934F 的區域。

當調用 swap 函數將 list_x 傳遞給形式參數 list 時,會將該地址直接傳遞過去,list 也會指向這個地址,如步驟二所示。

最后,由於列表是可變的,所以當 list 在向列表中添加元素時,list_x 自然會受到影響,因為二者指向的是同一塊內存。

所以,這里也是值傳遞,只不過傳遞的值是對象的內存地址罷了。

第二個關於 list 的例子中,我們對 swap 函數進行了修改,其執行流程如下圖所示。

在執行 swap 函數之前都與上面的例子毫無差別。在 swap 函數內部 list = list + [4] 表示新建一個末尾加入元素 4 的新的列表,並讓 list 指向這個新的內存地址 OX7686936A。因為是生成了一個新的對象,與原對象無關,所以 list_x 不受影響。

簡而言之,弄清楚改變變量和重新賦值的區別就好了,第一個例子中我們改變了變量的值,所以當函數執行結束后所有指向該對象的變量都會受影響。而重新賦值相當於重新生成一個新的對象並在新的對象上做操作,因此舊對象不受影響。

如果我們要想在函數中改變對象,第一可以傳入可變數據類型(列表,字典,集合),直接改變;第二還可以創建一個新的對象,修改后返回。建議用后者,表達清晰明了,不易出錯。

總結
本文介紹了 Python 函數的參數傳遞機制。理解了參數的傳遞過程和底層實現細節,寫代碼時將會少犯一些不必要的低級錯誤。

最后,無論是值傳遞還是引用傳遞,我們只需關注函數內部是否會生成新的對對象即可。凡是對原對象操作的函數,都會影響傳遞的實際參數;凡是生成了新對象的操作,都不會影響傳遞的實際參數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM