1. 程式人生 > >Python 深入理解賦值、引用、拷貝

Python 深入理解賦值、引用、拷貝

在 python 中賦值語句總是建立物件的引用值,而不是複製物件。因此,python 變數更像是指標,而不是資料儲存區域


這點和大多數 OO 語言類似吧,比如 C++、java 等。

首先來看個問題

在Python中,令values=[0,1,2];values[1]=values,為何結果是[0,[...],2]?

>>> values = [0,1,2]
>>> values[1] = values
>>> values
[0, [...], 2]

我預想的是

[0,[0,1,2],2]

但結果卻賦值了無限次

問題在於Python沒有賦值,只有引用,上面這個操作相當於建立了一個引用自身的結構,所以導致了無限迴圈。

下面這個例子可以幫助理解:

首先執行

values = [0,1,2]

Python 做的事情是首先建立一個列表物件 [0, 1, 2],然後給它貼上名為 values 的標籤。如果隨後又執行

values = [3,4,5]

Python 做的事情是建立另一個列表物件 [3, 4, 5],然後把剛才那張名為 values 的標籤從前面的 [0, 1, 2] 物件上撕下來,重新貼到 [3, 4, 5] 這個物件上。

至始至終,並沒有一個叫做 values 的列表物件容器存在,Python 也沒有把任何物件的值複製進 values 去。過程如圖所示:


現在我們回到之前那個問題,當執行

values[1] = values

Python做的事情則是把 values 這個標籤所引用的列表物件的第二個元素指向 values 所引用的列表物件本身。執行完畢後,values 標籤還是指向原來那個物件,只不過那個物件的結構發生了變化,從之前的列表 [0, 1, 2] 變成了 [0, ?, 2],而這個 ? 則是指向那個物件本身的一個引用。如圖所示:


要達到你所需要的效果,即得到 [0, [0, 1, 2], 2] 這個物件,你不能直接將 values[1] 指向 values 引用的物件本身,而是需要吧 [0, 1, 2] 這個物件「複製」一遍,得到一個新物件,再將 values[1] 指向這個複製後的物件。Python 裡面複製物件的操作因物件型別而異,複製列表 values 的操作是

values[:] #生成物件的拷貝或者是複製序列,不再是引用和共享變數,但此法只能頂層複製

所以你需要執行

values[1] = values[:]

Python 做的事情是,先 dereference 得到 values 所指向的物件 [0, 1, 2],然後執行 [0, 1, 2][:] 複製操作得到一個新的物件,內容也是 [0, 1, 2],然後將 values 所指向的列表物件的第二個元素指向這個複製二來的列表物件,最終 values 指向的物件是 [0, [0, 1, 2], 2]。過程如圖所示:


往更深處說,values[:] 複製操作是所謂的「淺複製」(shallow copy),當列表物件有巢狀的時候也會產生出乎意料的錯誤,比如

a = [0, [1, 2], 3]
b = a[:]
a[0] = 8
a[1][1] = 9

問:此時 a 和 b 分別是多少?

正確答案是 a 為 [8, [1, 9], 3],b 為 [0, [1, 9], 3]。需要注意的是:b 的第二個元素也被改變了


正確的複製巢狀元素的方法是進行「深複製」(deep copy),方法是

import copy

a = [0, [1, 2], 3]
b = copy.deepcopy(a)
a[0] = 8
a[1][1] = 9

Python變數作用域

可變物件 & 不可變物件

在Python中,物件分為兩種:可變物件和不可變物件,不可變物件包括int,float,long,str,tuple等,可變物件包括list,set,dict等。需要注意的是:這裡說的不可變指的是值的不可變。對於不可變型別的變數,如果要更改變數,則會建立一個新值,把變數繫結到新值上,而舊值如果沒有被引用就等待垃圾回收。另外,不可變的型別可以計算hash值,作為字典的key。可變型別資料對物件操作的時候,不需要再在其他地方申請記憶體,只需要在此物件後面連續申請(+/-)即可,也就是它的記憶體地址會保持不變,但區域會變長或者變短。

>>> a = 'a1'
>>> id(a)
140364890935624
>>> a = 'b1'
>>> id(a)
140364890935680

可以看出重新賦值之後,變數a的記憶體地址已經變了。 'a1'是str型別,不可變,所以賦值操作知識重新建立了str型別的 'a2'物件,然後將變數a指向了它。

>>> a_list = [1,2,3]
>>> id(a_list)
140364890927496
>>> a_list.append(4)
>>> id(a_list)
140364890927496

list重新賦值之後,變數a_list的記憶體地址並未改變,由於[1, 2, 3]是可變的,append操作只是改變了其value,變數a_list指向沒有變。

函式值傳遞

例子1:

>>> def func_int(a):
...     a += 4
...
>>> t = 0
>>> func_int(t)
>>> print(t)
0

例子2:

>>> def func_list(a_list):
...     a_list[0] = 4
...
>>> t_list = [1,2,3]
>>> func_list(t_list)
>>> print(t_list)
[4, 2, 3]

第一個例子看起來像是傳值,而第二個例子確實傳引用。其實,解釋這個問題也非常容易,主要是因為可變物件和不可變物件的原因:對於可變物件,物件的操作不會重建物件,而對於不可變物件,每一次操作就重建新的物件

在函式引數傳遞的時候,Python其實就是把引數裡傳入的變數對應的物件的引用依次賦值給對應的函式內部變數。參照上面的例子來說明更容易理解,func_int中的區域性變數"a"其實是全部變數"t"所指向物件的另一個引用,由於整數物件是不可變的,所以當func_int對變數"a"進行修改的時候,實際上是將區域性變數"a"指向到了整數物件"1"。所以很明顯,func_list修改的是一個可變的物件,區域性變數"a"和全域性變數"t_list"指向的還是同一個物件。

本文轉載於:https://www.cnblogs.com/jiangzhaowei/p/5740913.html

相關推薦

python 深入理解 引用拷貝作用域

似的 list 參數傳遞 question net 做的 標準 理解 官方 python 深入理解 賦值、引用、拷貝、作用域 在 python 中賦值語句總是建立對象的引用值,而不是復制對象。因此,python 變量更像是指針,而不是數據存儲區域, 這點

Python 深入理解引用拷貝

在 python 中賦值語句總是建立物件的引用值,而不是複製物件。因此,python 變數更像是指標,而不是資料儲存區域這點和大多數 OO 語言類似吧,比如 C++、java 等。首先來看個問題在Python中,令values=[0,1,2];values[1]=values

Python中的引用和深淺拷貝

全域性變數 在函式之外建立的變數屬於__main__,又被稱為全域性變數。它們可以在__main__中的任意函式中訪問,與區域性變數在函式結束時消失不同,全域性變數可以在不同函式的呼叫之間持久存在。全域性變數常常用作標誌(Flags)。標誌是一種布林型變數,可以

python引用和深拷貝

按引用賦值而不是拷貝副本 在python中,無論是直接的變數賦值,還是引數傳遞,都是按照引用進行賦值的。 在計算機語言中,有兩種賦值方式:按引用賦值、按值賦值。其中按引用賦值也常稱為按指標傳值(當然,它們還是有點區別的),後者常稱為拷貝副本傳值。它們的區別,詳細內容參見:按值傳遞 vs. 按指標傳遞。

深入理解PHP中引用

str ring int 之前 不同 重新 small nts 計數 【原文】 先看下面的問題: 1 2 3 4 5 6 7 8 <?php $a = 10;//將常量值賦給變量,會為a分配內存空間 $b = $a;//變量賦值給變量,是不是

少說話多寫程式碼之Python學習023——語句02(鏈式增量

鏈式賦值是將同一個值賦給多個變數的快捷方式。雖然賦值給了多個變數但是,其實處理的只有一個值。例如, #鏈式賦值 a=b= '天地有正氣,雜然賦流形。' print(a,b) c='正氣歌' d=c print(c,d) 輸出 天地有正氣,雜然賦流形。 天地有正氣,雜然賦流形。 正氣

少說話多寫程式碼之Python學習023——語句的使用者02(鏈式增量

鏈式賦值是將同一個值賦給多個變數的快捷方式。雖然賦值給了多個變數但是,其實處理的只有一個值。例如, #鏈式賦值 a=b= '天地有正氣,雜然賦流形。' print(a,b) c='正氣歌' d=c

深入理解 JavaScript中的變數傳參

1. demo 如果你對下面的程式碼沒有任何疑問就能自信的回答出輸出的內容,那麼本篇文章就不值得你浪費時間了。 var var1 = 1 var var2 = true var var3 = [1,2,3] var var4 = var3 function test (var1, var3) {

python基礎之算術複合運算子與常用的資料型別轉換

一**、算術運算子** 運算子為 +、加 -、減 、 乘 /、除 //、取整除 % 、取餘 /、冪 ** 二、 賦值運算子** = 賦值運算子 把等於號= 右邊的結果給左邊的變數 三、複合賦值運算子 += 加法賦值運算子 c += a 等效於 c = c + a -= 減法賦值運算子 c -

機器學習之深入理解神經網路理論基礎BP演算法及其Python實現

  人工神經網路(Artificial Neural Networks,ANN)系統是 20 世紀 40 年代後出現的。它是由眾多的神經元可調的連線權值連線而成,具有大規模並行處理、分散式信 息儲存、良

物件陣列傳遞引用傳遞

開發過程中經常會遇到如下情況,將一個物件賦值給另一個物件,修改後者,前者也隨之改變,場景程式碼如下: 控制檯輸出入下圖: 造成以上現象的原因,個人總結如下: 物件,陣列都是引用型別資料,在上述賦值操作過程中,僅僅是將儲存在棧中的路徑進行的賦值,而未對堆中的資料進行賦

python裏的拆包引用遞歸與匿名函數

python拆包:*A拆元組,**B拆字典。 引用:在C、C++裏面裏面a=1,b=a,實際上相當於硬鏈接,相當於兩份a、b各有一個1,多占一個空間,而在python裏就是軟連接,只有一份,通過id(a)來查看id都一樣, 在python裏定義的東西如一直沒引用,那麽就會成為垃圾,不用擔心,python有

【opencv 原始碼剖析】 四 Mat的建構函式 和 拷貝建構函式

1.賦值建構函式   右值引用 inline Mat& Mat::operator = (Mat&& m) { if (this == &m) return *this; release(); flags = m.fl

物件的解構(字串布林函式)

解構的值,必須要為物件的key值,不然會為undefined   let {key1,key2,key3:k3} = objk3就代表key3 ,當寫了:k3時,key3就失效就報錯。  ({key1:obj2.xx,key2:arr[n]} = obj)                       

深入理解計算機作業系統學習筆記

前言 《深入理解計算機作業系統》這本書是作業系統領域內非常經典的書,只要在網上搜索作業系統的書籍,必然會有人推薦這一本書,這本書也被各路大牛所推薦。雖然之前在圖書館借過這本書,但是匆匆看了一遍,只是記住了皮毛,最近下決心買了本紙質版的書,準備重讀經典,順便寫下讀書的收穫與思考。 這本

python進階之Python引用

==, is: == 判斷的是值, is 判斷的是記憶體地址 (即物件的id) 小整數物件: [-5, 256] 練習1: 說出執行結果 def extendList(val, lst=[]): lst.append(val) ret

深入理解二分查詢(二二分答案)

二分答案     如果已知候選答案的範圍[min,max],有時候我們不必通過計算得到答案,只需在此範圍內應用“二分”的過程,逐漸靠近答案(最後,得到答案)! 一、何時可以使用“二分答案”     不是任何題目都適合使用“二分答案”的,我Sam觀察到一般有以下的一

深入理解ES6--迭代器生成器代理反射Promise

迭代器(Iterator)和生成器(Generator) for-of迴圈及展開運算子…都是針對迭代器的!!! 不能使用箭頭函式來建立生成器;ES6函式的簡寫方式可以(只需在函式名前加星號) 可迭代物件具有Symbol.iterator屬性,ES6

python變數引用拷貝之間的關係

變數和引用的關係 Python中一切皆為物件,不管是集合變數還是數值型or字串型的變數都是一個引用,都指向對應記憶體空間中的物件。 不可變物件:int,float,long,str,tuple等

C++11:深入理解引用,move語義和完美轉發

深入右值引用,move語義和完美轉發 轉載請註明:http://blog.csdn.net/booirror/article/details/45057689 乍看起來,move語義使得你可以用廉價的move賦值替代昂貴的copy賦值,完美轉發使得你可以將傳來的任意