在詳細的瞭解python中賦值、copy和deepcopy以前,咱們仍是要花一點時間來了解一下python內存中變量的存儲狀況。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中數據的存儲是下圖這種狀況,每一個變量中都存儲了這個變量的地址,而不是值自己;對於複雜的數據結構來講,裏面的存儲的也只只是每一個元素的地址而已。:ide
1.數據類型從新初始化對python語義引用的影響spa
變量的每一次初始化,都開闢了一個新的空間,將新內容的地址賦值給變量。對於下圖來講,咱們重複的給str1賦值,其實在內存中的變化以下右圖:3d
從上圖咱們能夠看出,str1在重複的初始化過程當中,是由於str1中存儲的元素地址由'hello world'的地址變成了'new hello world'的。指針
2.數據結構內部元素變化重對python語義引用的影響code
對於複雜的數據類型來講,改變其內部的值對於變量的影響:對象
當對列表中的元素進行一些增刪改的操做的時候,是不會影響到lst1列表自己對於整個列表地址的,只會改變其內部元素的地址引用。但是當咱們對於一個列表從新初始化(賦值)的時候,就給lst1這個變量從新賦予了一個地址,覆蓋了本來列表的地址,這個時候,lst1列表的內存id就發生了改變。上面這個道理用在全部複雜的數據類型中都是同樣的。
搞明白了上面的內容,再來探討變量的賦值,就變得很是簡單了。
1.str的賦值
咱們剛剛已經知道,str1的再次初始化(賦值)會致使內存地址的改變,從上圖的結果咱們能夠看出修改了str1以後,被賦值的str2從內存地址到值都沒有受到影響。
看內存中的變化,起始的賦值操做讓str1和str2變量都存儲了‘hello world’所在的地址,從新對str1初始化,使str1中存儲的地址發生了改變,指向了新建的值,此時str2變量存儲的內存地址並未改變,因此不受影響。
2.複雜的數據結構中的賦值
剛剛咱們看了簡單數據類型的賦值,如今來看複雜數據結構變化對應內存的影響。
上圖對列表的增長修改操做,沒有改變列表的內存地址,lst1和lst2都發生了變化。
對照內存圖咱們不難看出,在列表中添加新值時,列表中又多存儲了一個新元素的地址,而列表自己的地址沒有變化,因此lst1和lst2的id均沒有改變而且都被添加了一個新的元素。
簡單的比喻一下,咱們出去吃飯,lst1和lst2就像是同桌吃飯的兩我的,兩我的公用一張桌子,只要桌子不變,桌子上的菜發生了變化兩我的是共同感覺的。
咱們已經詳細瞭解了變量賦值的過程。對於複雜的數據結構來講,賦值就等於徹底共享了資源,一個值的改變會徹底被另外一個值共享。
然而有的時候,咱們恰恰須要將一份數據的原始內容保留一份,再去處理數據,這個時候使用賦值就不夠明智了。python爲這種需求提供了copy模塊。提供了兩種主要的copy方法,一種是普通的copy,另外一種是deepcopy。咱們稱前者是淺拷貝,後者爲深拷貝。
深淺拷貝一直是全部編程語言的重要知識點,下面咱們就從內存的角度來分析一下二者的區別。
首先,咱們來了解一下淺拷貝。淺拷貝:無論多麼複雜的數據結構,淺拷貝都只會copy一層。下面就讓咱們看一張圖,來了解一下淺淺拷貝的概念。
看上面兩張圖,咱們加入左圖表示的是一個列表sourcelist,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個字符串和一個列表所在內存的地址。
咱們看下面分別對兩個列表進行的操做,紅色的框框裏面是變量初始化,初始化了上面的兩個列表;咱們能夠分別對這兩個列表進行操做,例如插入一個值,咱們會發現什麼呢?以下所示:
從上面的代碼咱們能夠看出,對於sourceLst和copyLst列表添加一個元素,這兩個列表好像是獨立的同樣都分別發生了變化,可是當我修改lst的時候,這兩個列表都發生了變化,這是爲何呢?咱們就來看一張內存中的變化圖:
咱們能夠知道sourceLst和copyLst列表中都存儲了一坨地址,當咱們修改了sourceLst1的元素時,至關於用'sourceChange'的地址替換了原來'str1'的地址,因此sourceLst的第一個元素髮生了變化。而copyLst仍是存儲了str1的地址,因此copyLst不會發生改變。
當sourceLst列表發生變化,copyLst中存儲的lst內存地址沒有改變,因此當lst發生改變的時候,sourceLst和copyLst兩個列表就都發生了改變。
這種狀況發生在字典套字典、列表套字典、字典套列表,列表套列表,以及各類複雜數據結構的嵌套中,因此當咱們的數據類型很複雜的時候,用copy去進行淺拷貝就要很是當心。。。
剛剛咱們瞭解了淺拷貝的意義,可是在寫程序的時候,咱們就是但願複雜的數據結構之間徹底copy一份而且它們之間又沒有一毛錢關係,應該怎麼辦呢?
咱們引入一個深拷貝的概念,深拷貝——即python的copy模塊提供的另外一個deepcopy方法。深拷貝會徹底複製原變量相關的全部數據,在內存中生成一套徹底同樣的內容,在這個過程當中咱們對這兩個變量中的一個進行任意修改都不會影響其餘變量。下面咱們就來試驗一下。
看上面的執行結果,這一次咱們不論是對直接對列表進行操做仍是對列表內嵌套的其餘數據結構操做,都不會產生拷貝的列表受影響的狀況。咱們再來看看這些變量在內存中的情況:
看了上面的內容,咱們就知道了深拷貝的原理。其實深拷貝就是在內存中從新開闢一塊空間,無論數據結構多麼複雜,只要遇到可能發生改變的數據類型,就從新開闢一塊內存空間把內容複製下來,直到最後一層,再也不有複雜的數據類型,就保持其原引用。這樣,無論數據結構多麼的複雜,數據之間的修改都不會相互影響。這就是深拷貝~~~