一、Python的變量及其存儲
在高級語言中,變量是對內存及其地址的抽象。對於python而言,python的一切變量都是對象,變量的存儲,采用了引用語義的方式,存儲的只是一個變量的值所在的內存地址,而不是這個變量的本身。
引用語義:在python中,變量保存的是對象(值)的引用,我們稱為引用語義。采用這種語義,變量所需的存儲空間大小一致,因為變量只是保存了一個引用。也被稱為對象語義和指針語義。
值語義:把變量的值直接保存在變量的存儲區里,這種方式稱為值語義。采用這種存儲方式,每一個變量在內存中所占的空間就要根據變量實際的大小而定,無法固定下來。
二、各基本上數據結構的地址存儲及改變情況
在python中的數據類型包括:bool、int、long、float、str、set、list、tuple、dict等等。這些數據類型可以分為簡單數據類型和復雜數據類型。
簡單數據類型和復雜數據類型的划分依據:如果一個數據類型,可以將其它的數據類型作為自己的元素,則可以認為這是一種數據結構。數據結構的分類有很多種,在Python中常用的只有集合、序列和映射三種結構。對應python中的set、list(tuple、str)、dict。常用的數據類型有int、long、float、bool、str等類型。
由於python中的變量都是采用的引用語義,數據結構可以包含基礎數據類型,導致了在python中的數據存儲方式存在下圖所示的這種情況,每個變量中都存儲了這個變量的地址,而不是值本身;對於復雜的數據結構來說。里面的存儲也只是每個元素的地址而已。
1. 數據類型重新初始化對python語義引用的影響
變量的每一次初始化,都開辟了一個新的空間,將新內容的地址賦值給變量。
>>> test = 'hello world' >>> id(test) 4363600816 >>> test = 'a new string' >>> id(test) 4363600880
2. 數據結構內部元素變化對python語義的影響
對於復雜的數據類型來說,改變其內部的值對於變量的影響:
>>> lst1 = [1,2,3,4,5] >>> id(lst1) 4363600328 >>> lst1.append('new item') >>> id(lst1) 4363600328 >>> lst1[0] = 'test item' >>> id(lst1) 4363600328 >>> lst1 = [1,2,3,4] >>> id(lst1) 4363600264
當對列表中的元素進行一些增刪改操作的時候,是不會影響到lst列表本身對於整個列表地址的,只會改變其內部元素的地址引用。可是當我們對於一個列表重新初始化(賦值)的時候,就給lst1這個變量重新賦予了一個地址,覆蓋了原本列表的地址,這個時候,lst1列表的內存id就發生了改變。
三、變量的賦值
1. 簡單數據類型的賦值
>>> str1 = 'hello world' >>> str2 = str1 >>> id(str1) 4363601072 >>> id(str2) 4363601072 >>> str1 = 'nihao' >>> id(str1) 4363608112 >>> id(str2) 4363601072
看內存的變化,起始的賦值操作讓str1和str2都存儲了‘hello world’所在的地址,重新對str1初始化,是str1中存儲的地址發生了改變,重新指向了新建的值,此時str2變量存儲的內存地址並未改變,所以不受影響。
2. 復雜數據結構中賦值
>>> lst1 = [1,2,3,4] >>> lst2 = lst1 >>> id(lst1) 4363600328 >>> id(lst2) 4363600328 >>> lst1[0] = 'test' >>> lst1 ['test', 2, 3, 4] >>> lst2 ['test', 2, 3, 4]
上述代碼增加修改操作,但是並沒有對lst2做出改變,結果lst1和lst2都發生了變化。
四、拷貝簡述
上述內容講述了變量賦值的過程。對於復雜的數據結構來說,賦值就等於完全共享了資源,一個值的改變會完全被另一個值共享。
然而有時候,我們偏偏需要將一份數據的原始內容保留一份,再去處理數據,這個時候使用賦值就不夠明智了。python為這種需求提供了copy模塊。提供了兩種主要的copy方法,一種是普通的copy,另一種是deepcopy。我們稱前者為淺拷貝,后者為深拷貝。
五、淺拷貝
淺拷貝:不管多么復雜的數據結構、淺拷貝都只會copy一層。
import copy lst = ['str1', 'str2', 'str3', 'str4', 'str5'] sourceLst = ['str1', 'str2', 'str3', 'str4', 'str5', lst] copyLst = copy.copy(sourceLst) print('1.->sourceLst:', sourceLst) print('1.->copyLst:', copyLst) sourceLst.append('sourcestr') copyLst.append('copystr') print('2.->sourceLst:', sourceLst) print('2.->copyLst:', copyLst) sourceLst[0] = 'changestr' print('3.->sourceLst:', sourceLst) print('3.->copyLst:', copyLst) lst.append('testAppend') print('4.->sourceLst:', sourceLst) print('4.->copyLst:', copyLst)
上述代碼執行會是下述結果:
1.->sourceLst: ['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5']] 1.->copyLst: ['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5']] 2.->sourceLst: ['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5'], 'sourcestr'] 2.->copyLst: ['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5'], 'copystr'] 3.->sourceLst: ['changestr', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5'], 'sourcestr'] 3.->copyLst: ['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5'], 'copystr'] 4.->sourceLst: ['changestr', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5', 'testAppend'], 'sourcestr'] 4.->copyLst: ['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5', 'testAppend'], 'copystr']
上述代碼中, sourceLst和copyLst中存儲的都是地址,當獨自修改各個list的時候,另一個不會改變。而當修改共有的lst元素的時候,sourceLst和copyLst都會發生改變,這種情況發生在字典套字典,列表套字典,字典套列表,列表套列表,以及各種復雜數據結構的嵌套中。
六、深拷貝
上述講述了淺拷貝,而在實際情況中,我們希望復雜的數據結構之間完全copy,而他們之間又沒有一毛錢關系。
為此,python引入了深拷貝的概念,我們可以使用copy模塊中的deepcopy方法。深拷貝會完全復制原變量相關的所有數據,在內存中生成一套完全一樣的內容,在這個過程中我們隊這兩個變量中的一個進行任意修改都不會影響其他變量。
淺拷貝的情況:
>>> lst1 = [1,2,3,4,[5,6,7,8]] >>> lst2 = lst1 # 實際進行了一個淺拷貝 >>> id(lst2) 4363600648 >>> id(lst1) 4363600648 >>> lst1[0] = 100 >>> lst1 [100, 2, 3, 4, [5, 6, 7, 8]] >>> lst2 [100, 2, 3, 4, [5, 6, 7, 8]] >>> lst2[4][0] = 100 >>> lst1 [100, 2, 3, 4, [100, 6, 7, 8]]
深拷貝的情況:
>>> import copy >>> lst1 = [1,2,3,4,[5,6,7,8]] >>> lst2 = copy.deepcopy(lst1) >>> id(lst1) 4363594504 >>> id(lst2) 4363593928 >>> lst1[0] = 100 >>> lst2 [1, 2, 3, 4, [5, 6, 7, 8]] >>> lst2[4][0]=100 >>> lst1 [100, 2, 3, 4, [5, 6, 7, 8]]
從上述代碼中可以看出,進行深拷貝后,lst1和lst2的地址不一樣,而且兩個獨自修改並不影響另一個的值,他倆現在一毛錢關系都沒有。
深拷貝就是在內存中重新開辟一塊空間,不管數據結構多么復雜,只要遇到可能發生改變的數據類型,就重新開辟一塊內存空間把內容復制下來,知道最后一層,不再有復雜的數據類型,就保持其原引用,這樣,不管數據結構多么復雜,數據之間的修改都不會相互影響。這就是深拷貝~