參考:http://www.cnblogs.com/Eva-J/p/5534037.html#undefined
一、python的變量及其存儲
在高級語言中,變量是對內存及其地址的抽象。對於python而言,python的一切變量都是對象,變量的存儲,採用了引用語義的方式,存儲的只是一個變量的值所在的內存地址,而不是這個變量的只本身。
引用語義與值語義詳解
引用語義:在python中,變量保存的是對象(值)的引用,我們稱爲引用語義。採用這種方式,變量所需的存儲空間大小一致,因爲變量只是保存了一個引用。也被稱爲對象語義和指針語義。
值語義:有些語言採用的不是這種方式,它們把變量的值直接保存在變量的存儲區裏,這種方式被我們稱爲值語義,例如C語言,採用這種存儲方式,每一個變量在內存中所佔的空間就要根據變量實際的大小而定,無法固定下來。
值語義和引用語義的區別:
值語義: 死的、 傻的、 簡單的、 具體的、 可複製的
引用語義: 活的、 聰明的、 複雜的、 抽象的、不可複製的
我們來看一張簡單易懂的圖理解一下python的引用語義和C語言值語義在內存中的存儲情況,左右兩個圖,分別表示了python中變量存儲與C語言中變量存儲區別:
二、各基本數據類型的地址存儲及改變情況
在python中的數據類型包括:bool、int、long、float、str、set、list、tuple、dict等等。我們可以大致將這些數據類型歸類爲簡單數據類型和複雜的數據結構。
如果一個數據類型,可以將其他的數據類型作爲自己的元素,我就認爲這是一種數據結構。數據結構的分類有很多種,但是在Python中常用的只有集合、序列和映射三種結構。對應python中的set、list(tuple、str)、dict;常用的數據類型有int、long、float、bool、str等類型。(其中,str類型比較特別,因爲從C語言的角度來說,str其實是一個char的集合,但是這與本文的關聯不大,所以我們暫時不談這個問題)
由於python中的變量都是採用的引用語義,數據結構可以包含基礎數據類型,導致了在python中數據的存儲是下圖這種情況,每個變量中都存儲了這個變量的地址,而不是值本身;對於複雜的數據結構來說,裏面的存儲的也只只是每個元素的地址而已
三、變量的賦值
在python中,對象的賦值就是簡單的對象引用,這點和C++不同。如下:
list_a = [1,2,3,"hello",["python","C++"]]
list_b = list_a
這種情況下,list_b和list_a是一樣的,他們指向同一片內存,list_b不過是list_a的別名,是引用。我們可以使用 list_b is list_a 來判斷,返回true,表明他們地址相同,內容相同。也可使用id(x) for x in list_a, list_b 來查看兩個list的地址。
賦值操作(包括對象作爲參數、返回值)不會開闢新的內存空間,它只是複製了新對象的引用。也就是說,除了list_b這個名字以外,沒有其它的內存開銷。修改了list_a,就影響了list_b;同理,修改了list_b就影響了list_a。
注:增強賦值以及共享引用:(應該是隻對list有用?)
x = x + y,x 出現兩次,必須執行兩次,性能不好,合併必須新建對象 x,然後複製兩個列表合併屬於複製/拷貝
x += y,x 只出現一次,也只會計算一次,性能好,不生成新對象,只在內存塊末尾增加元素,當 x、y 爲list時, += 會自動調用 extend 方法進行合併運算
四、拷貝
我們已經詳細瞭解了變量賦值的過程。對於複雜的數據結構來說,賦值就等於完全共享了資源,一個值的改變會完全被另一個值共享。
然而有的時候,我們偏偏需要將一份數據的原始內容保留一份,再去處理數據,這個時候使用賦值就不夠明智了。python爲這種需求提供了copy模塊。提供了兩種主要的copy方法,一種是普通的copy,另一種是deepcopy。我們稱前者是淺拷貝,後者爲深拷貝。
深淺拷貝一直是所有編程語言的重要知識點,下面我們就從內存的角度來分析一下兩者的區別。
淺拷貝
不管多麼複雜的數據結構,淺拷貝都只會copy一層。[:]就是淺拷貝
看上圖,sourcelist = [‘str1’,’str2’,’str3’,’str4’,’str5’,[‘str1’,’str2’,’str3’,’str4’,’str5’]];淺拷貝的copylist,copylist = [‘str1’,’str2’,’str3’,’str4’,’str5’,[‘str1’,’str2’,’str3’,’str4’,’str5’]];
sourcelist和copylist表面上看起來一模一樣,但是實際上在內存中已經生成了一個新列表,copy了sourceLst,獲得了一個新列表,存儲了5個字符串和一個列表所在內存的地址。
但是:如果對sourcelist[5]進行增刪改時,copylist也會相應改變,因爲他們指向相同的地址
淺拷貝有三種形式:切片操作,工廠函數,copy模塊中的copy函數
切片操作:list_b = list_a[:] 或者 list_b = [each for each in list_a]
工廠函數:list_b = list(list_a)
copy函數:list_b = copy.copy(list_a)
![這裏寫圖片描述](https://img-blog.csdn.net/20180326100033974?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FkX2x0Zg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
深拷貝
深拷貝——即python的copy模塊提供的另一個deepcopy方法。深拷貝會完全複製原變量相關的所有數據,在內存中生成一套完全一樣的內容,在這個過程中我們對這兩個變量中的一個進行任意修改都不會影響其他變量。
其實深拷貝就是在內存中重新開闢一塊空間,不管數據結構多麼複雜,只要遇到可能發生改變的數據類型,就重新開闢一塊內存空間把內容複製下來,直到最後一層,不再有複雜的數據類型,就保持其原引用。這樣,不管數據結構多麼的複雜,數據之間的修改都不會相互影響。
其實深拷貝就是在內存中重新開闢一塊空間,不管數據結構多麼複雜,只要遇到可能發生改變的數據類型,就重新開闢一塊內存空間把內容複製下來,直到最後一層,不再有複雜的數據類型,就保持其原引用。這樣,不管數據結構多麼的複雜,數據之間的修改都不會相互影響。
關於拷貝的警告
- 對於非容器類型,如數字,字符,以及其它“原子”類型,沒有拷貝一說。產生的都是原對象的引用。
- 如果元組變量值包含原子類型對象,即使採用了深拷貝,也只能得到淺拷貝
五、陷阱:使用可變的默認參數
def foo(a, b, c=[]):
# append to c
# do some more stuff
永遠不要使用可變的默認參數,可以使用如下的代碼代替:
def foo(a, b, c=None):
if c is None:
c = []
# append to c
# do some more stuff
與其解釋這個問題是什麼,不如展示下使用可變默認參數的影響:
In[2]: def foo(a, b, c=[]):
... c.append(a)
... c.append(b)
... print(c)
...
In[3]: foo(1, 1)
[1, 1]
In[4]: foo(1, 1)
[1, 1, 1, 1]
In[5]: foo(1, 1)
[1, 1, 1, 1, 1, 1]
同一個變量c在函數調用的每一次都被反覆引用。這可能有一些意想不到的後果。