賦值、淺拷貝、深拷貝的理解
(一)2個為什麼
先通過2個為什麼來了解一下python記憶體中變數的儲存情況。
>>> name = [1,2,3,["alex","rain"]] >>> name2 = name.copy() # 將原列表copy一份賦值給name2 >>> print(name) [1, 2, 3, ['alex', 'rain']] >>> print(name2) [1, 2, 3, ['alex', 'rain']] >>>
name與name2相同
第一個為什麼:
>>> name[1] = -2 >>> print(name) [1, -2, 3, ['alex', 'rain']] >>> print(name2) [1, 2, 3, ['alex', 'rain']] >>>
name[1]改變後,name改變了而name2沒有改變,為什麼?
第二個為什麼:
>>> name[3][0] = "ALEX" >>> print(name) [1, -2, 3, ['ALEX', 'rain']] >>> print(name2) [1, 2, 3, ['ALEX', 'rain']] >>>
將name[3][0]的值改後,name改變了,name2也改變了,為什麼?
第一個為什麼和第二個為什麼都對列表進行了更改,而結果為什麼不一樣尼?
首先我們要清楚,列表name的元素是存在於多塊記憶體空間中的,不是在同一塊;每個元素的記憶體地址都是獨立的。
變數name等於[1,2,3,["alex","rain"]]這個含有這些元素的列表的時候,記憶體中發生了2件事:一是變數name是個列表,開闢了一塊記憶體空間;二是列表裡的每一個元素各自開闢了屬於自己的記憶體空間。name列表開闢的記憶體空間裡存的不是元素,而是每個元素的記憶體地址。每一個元素比作是家的話,name列表的記憶體空間裡存的就是郵寄到你家的包裹上的地址,是一個指向。
name2 copy name,只是copy了name中的元素(一級元素)的記憶體地址,即將id(1),id(2),id(3)等這些元素的記憶體地址複製到了name2的記憶體空間裡了。
解答第一個為什麼:
將name中的2改為-2,name內的2的記憶體地址會被擦除,然後將新開闢的-2的記憶體地址佔位到此處。由於2的記憶體地址還被name2引用,所以2的記憶體不會被釋放,依然存在。
對於第一個為什麼,name的第一個元素為-2,name2的第一個元素為2,因為這2個的記憶體地址不同,所以地址所指向的資料(在記憶體裡以十六進位制數表示)就不同。記憶體地址不同,資料不同。
解答第二個為什麼:
要區分清楚列表的記憶體地址和列表中元素的記憶體地址是不一樣的,不要混淆。
name2 copy name的時候,copy了每個元素(一級元素)的記憶體地址。name中元素['alex', 'rain']這個小列表的記憶體地址也被複制,即name中的小列表的記憶體地址與name2中的小列表的記憶體地址是一樣的,也就是說name2中的小列表沒有開闢新的空間,而是引用了name中小列表的空間。id(name[3]) = id(name2[3]).
第二個為什麼中,對name內的小列表中的元素"alex"進行了全大寫更改(過程:"ALEX"開闢了一塊新的空間,將這塊新空間的地址放到小列表中的索引為0的位置),即小列表(巢狀列表)內的元素的發生改變,而小列表的記憶體地址沒有發生變化,name2中的小列表的記憶體地址與name中的小列表的記憶體地址一樣,所以name中小列表的值發生變化,name2中的小列表的值也會變化。記憶體地址相同,指向的資料也相同。
(二)賦值、淺拷貝、深拷貝
1、賦值:傳遞物件的引用而已,原始列表name改變,被賦值的n也會做相同的改變。(見下圖,圖畫的不好)
2.淺拷貝:拷貝父物件,不會拷貝物件的內部的子物件。即拷貝列表name裡面的一級元素的記憶體地址,不拷貝name裡的小列表裡的元素的記憶體地址。
3.深拷貝:copy 模組的 deepcopy 方法,完全拷貝了父物件及其子物件。即name2不僅拷貝了name中一級元素(1,2,3,["alex","rain"])的的記憶體地址,也拷貝了巢狀列表,["alex","rain"]裡面的"alex"和"rain"的記憶體地址。
name比作是一個容器的話,我們把name裡的每樣東西複製了一份放到另一個容器name2裡。name裡有東西丟失的話,name2裡的還在,而name2這個容器是新開闢的空間。在第一個容器裡的東西還未變化之前,2個不同容器裝了同樣的東西。
非容器型別的沒有拷貝這一說。