Python學習之函式引數傳遞:傳值 or 引用 ?
在學完Python
函式那一章節時,很自然的的就會想到Python
中函式傳參時傳值呢?還是傳引用?或者都不是? 我回去看了看我以前做的關於淺拷貝與深拷貝的筆記,其實那裡也已經涉及了一些引用相關的問題了。不過在這裡還是再進行一次總結吧。
在回答上面的問題之前我們先來看看下面的程式碼:
程式碼1:
def foo(var):
var = 2
print(var) #output: 2
a = 1
foo(a)
print(a) #output: 1
恩,看似是值傳遞
程式碼2:
def bar(var):
var.append(1)
b = []
print(b) #output:[]
bar(b)
print(b) #output:[1]
應該是引用傳遞?有點奇怪吧,為了弄清楚這個問題,我們先來了解一下Python
中變數與物件的關係。
一、變數和物件
我們首先要知道Python
中的“變數”與C/C++
中“變數”是不同的。
在C/C++
中,當你初始化一個變數時,就是宣告一塊儲存空間並寫入值。相當於把一個值放入一個盒子裡:
int a = 1;
現在a
盒子裡放了一個整數1
,當給變數a
賦另外一個值時會替換盒子a
裡面的內容:
a = 2;
當你把變數a
賦給另外一個變數時,會拷貝a
盒子中的值並放入一個新的“盒子”裡:
int b = a;
但是
在Python
a = 1
現在變數a
指向了記憶體中的一個int
型的物件(a
相當於物件的標籤)。如果給a
重新賦值,那麼“標籤” a
將會移動並指向另一個物件:
a = 2
原來的值為1
的int
型物件仍然存在,但我們不能再通過a
這個識別符號去訪問它了(當一個物件沒有任何標籤或引用指向它時,它就會被自動釋放)。如果我們把變數a
賦給另一個變數,我們只是給當前記憶體中物件增加一個“標籤”而已:
b = a
綜上所述,在Python
中變數只是一個標籤,一個識別符號,它指向記憶體中的物件。故變數並沒有型別,型別是屬於物件的,這也是Python
二、可變物件與不可變物件
在Python
的基本資料型別中,我們知道numbers
、strings
和tuples
是不可更改的物件,而list
、dict
是可以修改的物件。那麼可變與不可變有什麼區別呢?看下面示例:
a = 1 # a指向記憶體中一個int型物件
a = 2 # 重新賦值
當將a
重新賦值時,因為原來值為1
的物件是不能改變的,所以a
會指向一個新的int
物件,其值為2
。(如下面的圖示)
示例2
list1 = [1, 2] # list1指向記憶體中一個list型別的物件
list1[0] = 2 # 重新賦值list1中第一個元素
因為list
型別是可以改變的,所以第一個元素變更為2
。更確切的說,list1
的第一個元素是int
型,重新賦值時一個新的int
物件被指定給第一個元素,但是對於list1
來說,它所指的列表型物件沒有變,只是列表的內容(其中一個元素)改變了。如下圖:
現在我們再來看看開始那兩段程式碼:
def foo(var):
var = 2
print(var)
a = 1
foo(a)
print(a)
上面這段程式碼把a
作為引數傳遞給函式,這時a
和var
都指向記憶體中值為1
的物件。然後在函式中var = 2
時,因為int
物件不可改變,於是建立一個新的int
物件(值為2
)並且令var
指向它。而a
仍然指向原來的值為1
的int
物件,所以函式沒有改變變數a
。
如下圖:
程式碼2;
def Bar(var):
var.append(1)
b = []
print(b)
Bar(b)
print(b)
這段程式碼把b
傳遞給函式Bar
,那麼b
和var
都會指向同一個list
型別的物件。因為list
物件是可以改變的,函式中使用append
在其末尾添加了一個元素,list
物件的內容發生了改變,但是b
和var
仍然是指向這一個list
物件,所以變數b
的內容也發生了改變。
如下圖:
那麼Python
中引數傳遞是傳值,還是傳引用呢?準確的回答:都不是。之所以不是傳值,因為沒有產生複製,而且函式擁有與呼叫者同樣的物件。而似乎更像是C++
的傳引用,但是有時卻不能改變實參的值。所以只能這樣說:對於不可變的物件,它看起來像C++中的傳值方式;對於可變物件,它看起來像C++中的按引用傳遞。