導讀: 1.變數和對象 2.可變對象與不可變對象 3.引用傳參 在C/C++中,傳值和傳引用是函數參數傳遞的兩種方式。由於思維定式,從C/C++轉過來的Python初學者也經常會感到疑惑:在Python中,函數參數傳遞是傳值,還是傳引用呢?看下麵兩段代碼: 看完第一段代碼,會有人說這是值傳遞,因為函 ...
導讀:
1.變數和對象
2.可變對象與不可變對象
3.引用傳參
在C/C++中,傳值和傳引用是函數參數傳遞的兩種方式。由於思維定式,從C/C++轉過來的Python初學者也經常會感到疑惑:在Python中,函數參數傳遞是傳值,還是傳引用呢?
看下麵兩段代碼:
def foo(arg): arg = 5 print(arg) x = 1 foo(x) # 輸出5 print(x) # 輸出1 def foo(arg): arg.append(3) x = [1, 2] print(x) # 輸出[1, 2] foo(x) print(x) # 輸出[1, 2, 3]
看完第一段代碼,會有人說這是值傳遞,因為函數並沒有改變x的值;看完第二段代碼,又會有人說這是傳引用,因為函數改變了x的內容。
那麼,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中,值是靠引用來傳遞來的。
我們可以用id()來判斷兩個變數是否為同一個值的引用。 我們可以將id值理解為那塊記憶體的地址標示。
>>> a = 1 >>> b = a >>> id(a) 13033816 >>> id(b) # 註意兩個變數的id值相同 13033816 >>> a = 2 >>> id(a) # 註意a的id值已經變了 13033792 >>> id(b) # b的id值依舊 13033816 >>> a = [1, 2] >>> b = a >>> id(a) 139935018544808 >>> id(b) 139935018544808 >>> a.append(3) >>> a [1, 2, 3] >>> id(a) 139935018544808 >>> id(b) # 註意a與b始終指向同一個地址 139935018544808View Code
二、可變對象與不可變對象
在Python的基本數據類型中,我們知道numbers、strings和tuples是不可更改的對象,而list、dict、set是可以修改的對象。那麼可變與不可變有什麼區別呢?看下麵示例:
a = 1 # a指向記憶體中一個int型對象 a = 2 # 重新賦值
當將a重新賦值時,因為原來值為1的對象是不能改變的,所以a會指向一個新的int對象,其值為2。(如上面的圖示)
lst = [1, 2] # lst指向記憶體中一個list類型的對象 lst[0] = 2 # 重新賦值lst中第一個元素
因為list類型是可以改變的,所以第一個元素變更為2。更確切的說,lst的第一個元素是int型,重新賦值時一個新的int對象被指定給第一個元素,但是對於lst來說,它所指的列表型對象沒有變,只是列表的內容(其中一個元素)改變了。
好了,到這裡我們就很容易解釋開頭的兩段代碼了:
def foo(arg): arg = 5 print(arg) x = 1 foo(x) # 輸出5 print(x) # 輸出1
上面這段代碼把x作為參數傳遞給函數,這時x和arg都指向記憶體中值為1的對象。然後在函數中arg = 5時,因為int對象不可改變,於是創建一個新的int對象(值為5)並且令arg指向它。而x仍然指向原來的值為1的int對象,所以函數沒有改變x變數。
def foo(arg): arg.append(3) x = [1, 2] print(x) # 輸出[1, 2] foo(x) print(x) # 輸出[1, 2, 3]
這段代碼同樣把x傳遞給函數foo,那麼x和arg都會指向同一個list類型的對象。因為list對象是可以改變的,函數中使用append在其末尾添加了一個元素,list對象的內容發生了改變,但是x和arg仍然是指向這一個list對象,所以變數x的內容發生了改變。
三、引用傳參
可變類型與不可變類型的變數分別作為函數參數時,會有什麼不同嗎?
Python有沒有類似C語言中的指針傳參呢?
>>> def selfAdd(a): ... """自增""" ... a += a ... >>> a_int = 1 >>> a_int 1 >>> selfAdd(a_int) >>> a_int 1 >>> a_list = [1, 2] >>> a_list [1, 2] >>> selfAdd(a_list) >>> a_list [1, 2, 1, 2]
Python中函數參數是引用傳遞(註意不是值傳遞)。對於不可變類型,因變數不能修改,所以運算不會影響到變數自身;而對於可變類型來說,函數體中的運算有可能會更改傳入的參數變數。
想一想為什麼
>>> def selfAdd(a): ... """自增""" ... a = a + a # 我們更改了函數體的這句話 ... >>> a_int = 1 >>> a_int 1 >>> selfAdd(a_int) >>> a_int 1 >>> a_list = [1, 2] >>> a_list [1, 2] >>> selfAdd(a_list) >>> a_list [1, 2] # 想一想為什麼沒有變呢?
總結:
x += x是直接對x指向的空間進行修改,而不是讓b指向一個新的。
x = x+x先把=號右邊的結果計算出來,然後讓x指向這個新的地方,不管原來b指向誰.
四、一切皆對象
Python使用對象模型來儲存數據,任何類型的值都是一個對象。所有的python對象都有3個特征:身份id、類型type和值value。
身份:每一個對象都有自己的唯一的標識,可以使用內建函數id()來得到它。這個值可以被認為是該對象的記憶體地址。
類型:對象的類型決定了該對象可以保存的什麼類型的值,可以進行什麼操作,以及遵循什麼樣的規則。type()函數來查看python 對象的類型。
值:對象表示的數據項。
>>> a = 1 >>> id(a) 140068196051520 >>> b = 2 >>> id(b) 140068196051552 >>> c = a >>> id(c) 140068196051520 >>> c is a True >>> c is not b True
運算符 is 、 is not 就是通過id()的返回值(即身份)來判定的,也就是看它們是不是同一個對象的“標簽”。
本文來源於我看到的一篇文檔,具體來源不可考,我覺得對於引用講的還是比較清楚的。引用以及可變對象不可變對象,在Python中時比較重要的,因為在接下來的學習中,都會有意無意地用到。