【python測試開發棧】—理解python深拷貝與淺拷貝的區別

內存的淺拷貝和深拷貝是面試時常常被問到的問題,若是不能理解其本質原理,有可能會答非所問,給面試官留下很差的印象。另外,理解淺拷貝和深拷貝的原理,還能夠幫助咱們理解Python內存機制。這篇文章將會經過一些例子,來驗證內存拷貝的過程,幫助你們理解內存拷貝的原理。python

Python3中的數據類型

咱們首先得知道Python3中的數據被分爲可變類型不可變類型面試

  • 可變類型:Number(數字)、String(字符串)、Tuple(元祖)
  • 不可變類型:List(列表)、Dictionary(字典)、Set(集合)

對於可變類型和不可變類型,它們在淺拷貝和深拷貝中的表現是不同的,下面咱們就經過具體的例子來引出對應的結論。數據結構

淺拷貝

咱們先來貼一個例子,而後你們能夠先思考下結果會是怎樣的。app

def shadow_copy_test():
    """
    對淺copy進行驗證
    :return:
    """
    # 不可變數據類型
    param_a = 17
    param_b = "paramB"
    param_c = (18, "paramC")

    copy_param_a = copy.copy(param_a)
    copy_param_b = copy.copy(param_b)
    copy_param_c = copy.copy(param_c)
    print("驗證不可變數據類型")
    print(id(param_a))
    print(id(copy_param_a))
    print(id(param_b))
    print(id(copy_param_b))
    print(id(param_c))
    print(id(copy_param_c))

    print("======================")

    # 可變數據類型
    param_d = [[2, 3], 18, "paramD"]
    param_e = {"key1": 18, "key2": "paramE", "key3": [1, 2]}
    param_f = {18, "paramF"}

    copy_param_d = copy.copy(param_d)
    copy_param_e = copy.copy(param_e)
    copy_param_f = copy.copy(param_f)
    
    print("驗證可變數據類型")
    print(id(param_d))
    print(id(copy_param_d))
    print(id(param_e))
    print(id(copy_param_e))
    print(id(param_f))
    print(id(copy_param_f))
    
    # 運行結果
    驗證不可變數據類型
    4455468864
    4455468864
    4457955120
    4457955120
    4457945040
    4457945040
    ======================
    驗證可變數據類型
    4458366368
    4458367168
    4457911312
    4457911552
    4457982144
    4458284768

由此咱們能夠看出,對於不可變類型,淺拷貝並不會更改內存地址,而對於可變數據類型,會產生一個新的內存地址。接下來咱們再來看看對於可變數據類型,去修改其中的元素會怎麼樣:spa

print("驗證列表中元素")
    # 驗證列表中第一個元素是否相等
    print(id(param_d[1]))
    print(id(copy_param_d[1]))
    print(id(param_d[0]))
    print(id(copy_param_d[0]))
	
  	print("======================")
    # 更改列表中元素的值
    print("驗證修改可變數據類型元素的值")
    param_d[0].append(4)
    print(param_d)
    print(copy_param_d)
    param_d.append("abc")
    print(param_d)
    print(copy_param_d)
    param_d[1] = 19
    print(param_d)
    print(copy_param_d)
    
    # 運行結果
    驗證列表中元素
    4534525792
    4534525792
    4537357968
    4537357968
    驗證修改可變數據類型元素的值
    [[2, 3, 4], 18, 'paramD']
    [[2, 3, 4], 18, 'paramD']
    [[2, 3, 4], 18, 'paramD', 'abc']
    [[2, 3, 4], 18, 'paramD']
    [[2, 3, 4], 19, 'paramD', 'abc']
    [[2, 3, 4], 18, 'paramD']

咱們從上面結果能夠看出,對於可變數據結構,他們元素的內存地址沒有變化(以List爲例,至關於新生成一個List,而後將原來List中元素的值所有copy到新生成的List中),而修改其中的可變數據類型(好比:param_d[0]),copy對象也會同步修改(copy_param_d[0]);而修改不可變元素(好比:param_d[1]),並不會影響其copy對象(copy_param_d[1])。code

綜上咱們能夠得出以下結論(敲黑板,劃重點):對象

  • 對於不可變數據類型,淺拷貝只是複製了內存引用(指向內存的地址),並不會開闢新的內存空間(上例中param_a和copy_param_a、param_b和copy_param_b、param_c和copy_param_c內存地址一致)。
  • 對於可變數據類型,淺拷貝會開闢新的內存空間(上例中param_d和copy_param_d內存地址不一致),可是它裏面元素的內存地址仍是同樣的。
  • 對於可變數據類型,改變原始對象中的可變數據類型的值,會同時影響拷貝對象的值(由於它們指向了對一個內存地址);改變原始對象中的不可變數據類型的值,不會影響拷貝對象的值。

爲了方便你們理解,畫了內存地址的簡圖:遞歸

首先是不可變數據類型,由於其值的內存地址是不可變的,因此在內存中只有這一份:內存

WX20191215-192332@2x.png

其次是可變數據類型:字符串

WX20191217-102045@2x.png

深拷貝

一樣的,咱們仍是先來看例子(代碼基本和上面的保持一致,只是修改了深拷貝方法deepcopy):

def deep_copy_test():
    """
    對深拷貝進行驗證
    :return:
    """
    """
       對淺copy進行驗證
       :return:
       """
    # 不可變數據類型
    param_a = 17
    param_b = "paramB"
    param_c = (18, "paramC")

    copy_param_a = copy.deepcopy(param_a)
    copy_param_b = copy.deepcopy(param_b)
    copy_param_c = copy.deepcopy(param_c)
    print("驗證不可變數據類型")
    print(id(param_a))
    print(id(copy_param_a))
    print(id(param_b))
    print(id(copy_param_b))
    print(id(param_c))
    print(id(copy_param_c))

    print("======================")

    # 可變數據類型
    param_d = [[2, 3], 18, "paramD"]
    param_e = {"key1": 18, "key2": "paramE", "key3": [1, 2]}
    param_f = {18, "paramF"}

    copy_param_d = copy.deepcopy(param_d)
    copy_param_e = copy.deepcopy(param_e)
    copy_param_f = copy.deepcopy(param_f)

    print("驗證可變數據類型")
    print(id(param_d))
    print(id(copy_param_d))
    print(id(param_e))
    print(id(copy_param_e))
    print(id(param_f))
    print(id(copy_param_f))
    print("======================")
    print("驗證列表中元素")
    # 驗證列表中第一個元素是否相等
    print(id(param_d[1]))
    print(id(copy_param_d[1]))
    print(id(param_d[0]))
    print(id(copy_param_d[0]))
    
 		print("======================")
    # 更改列表中元素的值
    print("驗證修改可變數據類型元素的值")
    param_d[0].append(4)
    print(param_d)
    print(copy_param_d)
    param_d.append("abc")
    print(param_d)
    print(copy_param_d)
    param_d[1] = 19
    print(param_d)
    print(copy_param_d)
    
    # 打印結果以下:
    驗證不可變數據類型
    4438175552
    4438175552
    4440636208
    4440636208
    4440885840
    4440885840
    ======================
    驗證可變數據類型
    4440987760
    4441335360
    4440593344
    4440594224
    4440966160
    4440967840
    ======================
    ======================
    驗證列表中元素
    4438175584
    4438175584
    4440628192
    4441336000
    驗證修改可變數據類型元素的值
    [[2, 3, 4], 18, 'paramD']
    [[2, 3], 18, 'paramD']
    [[2, 3, 4], 18, 'paramD', 'abc']
    [[2, 3], 18, 'paramD']
    [[2, 3, 4], 19, 'paramD', 'abc']
    [[2, 3], 18, 'paramD']

咱們能夠和淺拷貝的運行結果作個對比,其中有差異的地方是:淺拷貝時列表中元素的內存地址沒變,而深拷貝時列表中元素的內存地址發生了變化(主要針對可變數據類型,好比:param_d[0]和copy_param_d[0])。另外,對於可變數據類型,修改原始數據中的值,並不會影響拷貝數據。

綜上,咱們得出以下結論(敲黑板,劃重點):

  • 深拷貝是在淺拷貝的基礎上,又對可變數據類型的元素進行了遞歸拷貝,所以拷貝完成時,對於可變數據類型的元素,其內存地址所有都不一致。
  • 深拷貝修改原始對象和拷貝對象的值,相互之間不影響。

爲了你們理解,一樣畫了一幅內存簡圖(主要是針對可變數據類型),能夠對比下和淺拷貝時內存簡圖的區別:

WX20191217-101852@2x.png

總結

本文主要介紹了在Python3中內存的深拷貝和淺拷貝機制,你們能夠動手寫一下文中貼的Python代碼,這樣更能加深你的理解。總結來講,對於Python的不可變數據類型,深拷貝和淺拷貝的差異不大;主要區別是Python中的可變數據類型,深拷貝會對列表中的子元素進行遞歸拷貝處理,而淺拷貝則不會。

相關文章
相關標籤/搜索