如果你曾經(jīng)在Python函數(shù)內(nèi)部修改過某個變量,然后對它在函數(shù)外部發(fā)生的變化感到困惑或驚訝,那么你并不是個例。這個問題也曾讓我困擾了很長時間。
由于之前學(xué)習(xí)的教程中提到了“按值傳遞”和“按引用傳遞”,我原本以為Python肯定遵循這兩種方式中的一種。但實際上并非如此。Python采用了一種略有不同的機制,一旦你理解了這一點,很多之前令人困惑的現(xiàn)象就會變得清晰起來。
在這篇文章中,你將了解到:
-
“按值傳遞”和“按引用傳遞”的含義
-
像C這樣的其他語言是如何處理這個問題的
-
Python實際上采用的是哪種機制(通過對象引用進(jìn)行傳遞)
-
可變類型和不可變類型如何影響函數(shù)內(nèi)部的行為
目錄
按值傳遞與按引用傳遞的解釋
在討論Python之前,我們先來快速定義這兩個概念。
按值傳遞意味著將變量的副本傳遞給函數(shù)。在函數(shù)內(nèi)部對這份副本進(jìn)行的任何操作都不會影響到原始變量。
按引用傳遞意味著將變量實際存儲的內(nèi)存地址傳遞給函數(shù)。因此,在函數(shù)內(nèi)部對變量進(jìn)行的修改會直接影響到原始變量。
許多語言都支持這兩種機制中的一種或兩種。然而Python并不采用這兩種方式中的任何一種——至少不是以傳統(tǒng)意義上的那種方式。
C語言中的實現(xiàn)方式及示例
C語言就是一個明確支持這兩種機制的語言例子。
以下是C語言中按值傳遞的示例。原始變量不會受到影響:
#include
void modify(int *n) {
*n = *n + 10;
printf("函數(shù)內(nèi)部: %d\n", *n); }
int main() {
int x = 5;
modify(&x);
printf("函數(shù)外部: %d\n", x);
return 0; }
輸出結(jié)果:
函數(shù)內(nèi)部: 15
函數(shù)外部: 15 ← 原始變量發(fā)生了變化!
在C語言中,你需要明確指定是傳遞變量的指針還是它的值。而Python并沒有給你這種選擇權(quán),但它所采用的機制實際上是非常合理的。
Python實際上采用的是哪種機制
Python采用了一種稱為通過對象引用進(jìn)行傳遞的機制(有時也被稱為“按賦值方式傳遞”)。
在Python中,當(dāng)你將一個變量傳遞給一個函數(shù)時,你實際上傳遞的是該變量所指向的對象的引用,而不是該值的副本,更不是變量本身。
接下來會發(fā)生什么,完全取決于該對象是可變的(可以在原位置上進(jìn)行修改)還是不可變的(不能在原位置上進(jìn)行修改)。
可變類型與不可變類型
在Python中,不可變類型包括int、float、str和tuple。這些對象不能在原位置上進(jìn)行修改。當(dāng)你在函數(shù)內(nèi)部“修改”其中一個對象時,Python會創(chuàng)建一個全新的對象,而原來的對象則保持不變。
def modify_number(n):
n = n + 10
print("函數(shù)內(nèi)部:", n)
x = 5
modify_number(x)
print("函數(shù)外部:", x)
輸出結(jié)果:
函數(shù)內(nèi)部: 15
函數(shù)外部: 15 ← 原始值未發(fā)生變化
可變類型包括list、dict和set。這些類型可以在原位置上進(jìn)行修改。當(dāng)你在函數(shù)內(nèi)部修改其中一個對象時,你實際上是在修改調(diào)用者所持有的那個對象的副本。
def modify_list(items):
items.append(99)
print("函數(shù)內(nèi)部:", items)
my_list = [1, 2, 3]
modify_list(my_list)
print("函數(shù)外部:", my_list)
輸出結(jié)果:
函數(shù)內(nèi)部: [1, 2, 3, 99]
函數(shù)外部: [1, 2, 3, 99] ← 原始對象發(fā)生了變化!
關(guān)鍵在于:Python并不會根據(jù)你傳遞數(shù)據(jù)的方式來決定其行為,而是會根據(jù)你傳遞的對象類型來判斷應(yīng)該發(fā)生什么。
結(jié)論
Python并不采用“按值傳遞”或“按引用傳遞”的方式。它采用的是對象引用傳遞,即函數(shù)接收的是對對象的引用,而該對象是否可以在原位置上進(jìn)行修改,才決定了后續(xù)會發(fā)生什么。
總結(jié)如下:
-
不可變類型(
int、str、tuple):在函數(shù)內(nèi)部會創(chuàng)建一個新對象,原始對象保持不變 -
可變類型(
list、dict、set):原始對象會被直接修改
一旦理解了這一點,很多“為什么Python會這樣處理”這樣的疑問就會變得合情合理了。如果你剛開始學(xué)習(xí)Python中的函數(shù),記住這個概念,它會幫你避免很多調(diào)試上的麻煩。