1. 程式人生 > >Python學習之函式引數傳遞:傳值 or 引用 ?

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

這裡寫圖片描述

原來的值為1int型物件仍然存在,但我們不能再通過a這個識別符號去訪問它了(當一個物件沒有任何標籤或引用指向它時,它就會被自動釋放)。如果我們把變數a賦給另一個變數,我們只是給當前記憶體中物件增加一個“標籤”而已:
b = a

這裡寫圖片描述

綜上所述,在Python中變數只是一個標籤,一個識別符號,它指向記憶體中的物件。故變數並沒有型別,型別是屬於物件的,這也是Python

中的變數可以被任何型別賦值的原因。

二、可變物件與不可變物件
Python的基本資料型別中,我們知道numbersstringstuples是不可更改的物件,而listdict是可以修改的物件。那麼可變與不可變有什麼區別呢?看下面示例:

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作為引數傳遞給函式,這時avar都指向記憶體中值為1的物件。然後在函式中var = 2時,因為int物件不可改變,於是建立一個新的int物件(值為2)並且令var指向它。而a仍然指向原來的值為1int物件,所以函式沒有改變變數a
如下圖:

這裡寫圖片描述

程式碼2;

def Bar(var):  
   var.append(1)  

b = []  
print(b)   
Bar(b)  
print(b)   

這段程式碼把b傳遞給函式Bar,那麼bvar都會指向同一個list型別的物件。因為list物件是可以改變的,函式中使用append在其末尾添加了一個元素,list物件的內容發生了改變,但是bvar仍然是指向這一個list物件,所以變數b的內容也發生了改變。
如下圖:

這裡寫圖片描述

那麼Python中引數傳遞是傳值,還是傳引用呢?準確的回答:都不是。之所以不是傳值,因為沒有產生複製,而且函式擁有與呼叫者同樣的物件。而似乎更像是C++的傳引用,但是有時卻不能改變實參的值。所以只能這樣說:對於不可變的物件,它看起來像C++中的傳值方式;對於可變物件,它看起來像C++中的按引用傳遞。