如果你曾經(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、strtuple。這些對象不能在原位置上進(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、dictset。這些類型可以在原位置上進(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é)如下:

  • 不可變類型intstrtuple):在函數(shù)內(nèi)部會創(chuàng)建一個新對象,原始對象保持不變

  • 可變類型list、dict、set):原始對象會被直接修改

一旦理解了這一點,很多“為什么Python會這樣處理”這樣的疑問就會變得合情合理了。如果你剛開始學(xué)習(xí)Python中的函數(shù),記住這個概念,它會幫你避免很多調(diào)試上的麻煩。

Comments are closed.