欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python?對象拷貝及深淺拷貝區(qū)別的詳細教程示例

 更新時間:2023年08月09日 16:56:18   作者:追憶MHyourh  
這篇文章主要介紹了Python?對象拷貝及深淺拷貝區(qū)別的詳細教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

Python 對象拷貝

在本篇文章中,會先介紹 Python 中對象的基礎概念,之后會提到對象的深淺拷貝以及區(qū)別。在閱讀后,應該掌握如下的內(nèi)容:

  • 理解變量、引用和對象的關系
  • 理解 Python 對象中 identity,type 和 value 的概念
  • 什么是 mutable 和 immutable 對象?以及它們和 hashable 的關系
  • 深淺拷貝的過程以及區(qū)別

1.變量,引用和對象

變量無類型,它的作用僅僅在某個時候引用了特定的對象而已,具體在內(nèi)存中就是一個指針,僅僅擁有指向?qū)ο蟮目臻g大小。

變量和對象的關系在于引用,變量引用對象后,也就對應了賦值的過程。

在 python 中一切皆為對象,具體在內(nèi)存中表示一塊內(nèi)存空間,每一個對象都會具有 identity,type 和 value 這三個內(nèi)容。

Identity, 一旦對象被創(chuàng)建后,Identity 的值便不會發(fā)生改變。在 Cpython 中,其值體現(xiàn)為內(nèi)存中保存對象的地址。is 操作符,比較對象是否相等就是通過這個值。通過 id() 函數(shù)查看它的整數(shù)形式。

Type, 和 Identity 一樣,在對象創(chuàng)建后,Type 也不會發(fā)生變化。它主要定義了一些可能支持的值和操作(如對列表來說,會有求長度的操作)。通過 type() 函數(shù)可以得到對象的類型。

Value,用于表示的某些對象的值。當對象在創(chuàng)建后值可以改變稱為 mutable,否則的話被稱為 immutable.

舉個例子,比如在 C 中,int x = 4 在內(nèi)存中,是先分配了一個 int 類型的內(nèi)存空間,然后把 4 放進空間內(nèi)。

而 Python 中,x = 4 正好相反,是為 4 分配了一塊的內(nèi)存空間,然后用 x 指向它。由于變量可以指向各種類型的對象,因此不需要像 C 一樣聲明變量。這也就是 Python 被稱為動態(tài)類型的意義。

并且在 Python 中,變量可以刪除,但對象是無法刪除的。

2.immutable 和 mutable 對象

immutable 對象擁有一個固定的值,包括 numbers, strings, tuples. 一個新的值被保存時,一個新的對象就會被創(chuàng)建。這些對象在作為常量的 hash 值中有著非常重要的作用,如作為字典的 key 時。

mutable 對象可以改變自身的值,但 id() 并不會發(fā)生改變。

當一些對象包含對其他對象的一些引用時,我們稱這些對象為 containers, 例如 list, tuple, dictionary 這些都是 containers. 這里需要注意的是,一個 immutable containers 可以包含對 mutable 對象的引用(如在 tuple 中包含一個 list)。 但這個對象仍然稱為 immutable 對象,因為 Identity 是不變的。

3.hashable 對象

當一個對象在生命周期內(nèi)(實現(xiàn)了 __hash__() 方法)hash 值不會發(fā)生改變,并可以與其他對象進行比較(實現(xiàn)了 __eq__() 方法),稱之為hashable 對象。

在 Python 內(nèi)置的 immutable 對象 大多數(shù)都是 hashable 對象。immutable containers(tuples, frozenset)在引用的對象都是 hashable 對象時,才是hashable 對象。mutable containers 容器都不是 hashable 對象。用戶自定義的類都是 hashable 對象,

4.淺拷貝與深拷貝

在介紹對象的拷貝前,先介紹一下 Python 中的賦值操作,可以讓我們更好的了解拷貝的過程。

5.賦值操作

賦值操作的右邊是簡單表達式:

def normal_operation():
    # immutable objects
    # int
    a = 10
    b = 10
    print('----- int')
    print("id of a:{} , id of b: {}".format(id(a), id(b)))
    # id of a:1777364320 , id of b: 1777364320
    print(a == b)  # True
    print(a is b)  # True
    # str
    str_a = '123'
    str_b = '123'
    print('----- str')
    print("id of a:{} , id of b: {}".format(id(str_a), id(str_b)))
    # id of a:1615046978224 , id of b: 1615046978224
    print(str_a == str_b)  # True
    print(str_a is str_b)  # True
    # tuple
    tuple_a = (1, 2, 3)
    tuple_b = (1, 2, 3)
    print('----- tuple')
    print("id of a:{} , id of b: {}".format(id(tuple_a), id(tuple_b)))
    # id of a:1615047009696 , id of b: 1615047024856
    print(tuple_a == tuple_b)  # True
    print(tuple_a is tuple_b)  # False
    # mutable
    # set
    set_a = {1, 2, 3}
    set_b = {1, 2, 3}
    print('----- set')
    print("id of a:{} , id of b: {}".format(id(set_a), id(set_b)))
    # id of a:1615045625000 , id of b: 1615047012872
    print(set_a == set_b)  # True
    print(set_a is set_b)  # False
    # list
    list_a = [1, 2, 3]
    list_b = [1, 2, 3]
    print('----- list')
    print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
    # id of a:1615047017800 , id of b: 1615045537352
    print(list_a == list_b)  # True
    print(list_a is list_b)  # False
    # dict
    dict_a = {"name": "xxx", "age": "123"}
    dict_b = {"name": "xxx", "age": "123"}
    print('----- dict')
    print("id of a:{} , id of b: {}".format(id(dict_a), id(dict_b)))
    # id of a:1615045521696 , id of b: 1615045522128
    print(dict_a == dict_b)  # True
    print(dict_a is dict_b)  # False

在 Cpython 中,id() 反映了對象在內(nèi)存中的地址??梢钥吹剑瑢τ?immutable 對象中的 number 和 string 來說,CPython 本身對其做了一定的優(yōu)化,在創(chuàng)建相同的內(nèi)容時,使其 指向了相同的內(nèi)存地址,從而被復用。

但是,Python 不會對所有 mutable 對象執(zhí)行此操作,因為實現(xiàn)此功能需要一定的運行時成本。對于在內(nèi)存中的對象來說,必須首先在內(nèi)存中搜索對象(搜索意味著時間)。對于 number 和 string 來說,搜索到它們很容易,所以才對其做了這樣的優(yōu)化。

對于其他類型的對象,雖然創(chuàng)建的內(nèi)容相同,但都在內(nèi)存中完全創(chuàng)建了一塊新的區(qū)域。

6. 賦值操作的右邊是 Python 中已存在的變量:

def assignment_operation():
    # immutable objects
    # int
    a = 10
    b = a
    print('----- int')
    print("id of a:{} , id of b: {}".format(id(a), id(b)))
    # id of a:1777364320 , id of b: 1777364320
    print(a == b)  # True
    print(a is b)  # True
    # str
    str_a = '123'
    str_b = str_a
    print('----- str')
    print("id of a:{} , id of b: {}".format(id(str_a), id(str_b)))
    # id of a:2676110142128 , id of b: 2676110142128
    print(str_a == str_b)  # True
    print(str_a is str_b)  # True
    # tuple
    tuple_a = (1, 2, 3)
    tuple_b = tuple_a
    print('----- tuple')
    print("id of a:{} , id of b: {}".format(id(tuple_a), id(tuple_b)))
    # id of a:2676110191640 , id of b: 2676110191640
    print(tuple_a == tuple_b)  # True
    print(tuple_a is tuple_b)  # True
    # mutable
    # set
    set_a = {1, 2, 3}
    set_b = set_a
    print('----- set')
    print("id of a:{} , id of b: {}".format(id(set_a), id(set_b)))
    # id of a:2676108788904 , id of b: 2676108788904
    print(set_a == set_b)  # True
    print(set_a is set_b)  # True
    # list
    list_a = [1, 2, 3]
    list_b = list_a
    print('----- list')
    print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
    # id of a:2676110181704 , id of b: 2676110181704
    print(list_a == list_b)  # True
    print(list_a is list_b)  # True
    # dict
    dict_a = {"name": "xxx", "age": "123"}
    dict_b = dict_a
    print('----- dict')
    print("id of a:{} , id of b: {}".format(id(dict_a), id(dict_b)))
    # id of a:2676079063328 , id of b: 2676079063328
    print(dict_a == dict_b)  # True
    print(dict_a is dict_b)  # True

而當賦值操作的右邊是已經(jīng)存在的 Python 對象時,不論是什么類型的對象,都沒有在內(nèi)存中創(chuàng)建新的內(nèi)容,僅僅是聲明了一個新的變量指向之前內(nèi)存中已經(jīng)創(chuàng)建的對象,就像提供了一個別名一樣。

>>> dict_a = {'1':1}
>>> dict_b = dict_a
>>> print("id of a:{} , id of b: {}".format(id(dict_a), id(dict_b)))
id of a:140355639151936 , id of b: 140355639151936
>>> dict_b = {}
>>> print("id of a:{} , id of b: {}".format(id(dict_a), id(dict_b)))
id of a:140355639151936 , id of b: 140355639922176

由于 dict_b = dict_a操作,讓兩個變量同時指向了同一塊內(nèi)存區(qū)域。自然 id 相等。
當對 dict_b 重新賦值時,僅讓 b 指向了另外一塊內(nèi)存區(qū)域,并不會影響 a 的指向,由于兩塊內(nèi)存區(qū)域不同,自然id 并不想等。

7.改變賦值后的對象

def assignment_operation_change():
    # immutable objects
    # int
    a = 10
    print("id of a:{}".format(id(a)))  
    # id of a:1994633728
    b = a 
    a = a + 10
    print('----- int')
    print("id of a:{} , id of b: {}".format(id(a), id(b)))
    # id of a:1994634048 , id of b: 1994633728
    print(a == b)  # False
    print(a is b)  # False
    # mutable objects
    # list
    list_a = [1, 2, 3]
    list_b = list_a
    list_a.append(4)
    print('----- list')
    print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
    # id of a:2676110181704 , id of b: 2676110181704
    print(list_a == list_b)  # True
    print(list_a is list_b)  # True

當修改 imutable 對象時,由于其本身不可改變,只能在內(nèi)存中新申請一塊新的空間,用于存儲修改后的內(nèi)容。對應上面 a=20 的操作,這時再判斷 a 和 b 時,由于指向了內(nèi)存的不同位置,所以 a,b不在相等。a 原來指向的內(nèi)存區(qū)域不會被回收,因為現(xiàn)在由 b 指向??梢钥吹?b 指向的內(nèi)存地址和 a 之前的指向的內(nèi)存地址是一致的。

當修改 mutable 對象時,由于都指向相同的內(nèi)存地址,所以對變量 list_a 修改的操作,也會映射到變量 list_b。

總結(jié)一下:

  • 指向 imutable 的不同變量,當其中一個變量被修改時,其他變量不受影響,因為被修改后的變量會指向一個新創(chuàng)建的對象。
  • 指向 mutable 對象的不同變量,當其中一個變量修改這個對象時,會影響到指向這個對象的所有變量。

8.淺拷貝

淺拷貝創(chuàng)建了一個對象,這個對象包含了對被拷貝元素的參考。 所以當使用淺拷貝來復制 conainters 對象時,僅僅拷貝了那些嵌套元素的引用。

def shallow_copy():
    # immutable objects
    # int
    a = 10
    b = copy(a)
    print('----- int')
    print("id of a:{} , id of b: {}".format(id(a), id(b)))
    # id of a:1777364320 , id of b: 1777364320
    print(a == b)  # True
    print(a is b)  # True
    # str
    str_a = '123'
    str_b = copy(str_a)
    print('----- str')
    print("id of a:{} , id of b: {}".format(id(str_a), id(str_b)))
    # id of a:2676110142128 , id of b: 2676110142128
    print(str_a == str_b)  # True
    print(str_a is str_b)  # True
    # tuple
    tuple_a = (1, 2, 3)
    # Three methods of shallow copy
    # tuple_b = tuple_a[:]
    # tuple_b = tuple(tuple_a)
    tuple_b = copy(tuple_a)
    print(id(tuple_b))
    print('----- tuple')
    print("id of a:{} , id of b: {}".format(id(tuple_a), id(tuple_b)))
    # id of a:2676110191640 , id of b: 2676110191640
    print(tuple_a == tuple_b)  # True
    print(tuple_a is tuple_b)  # True
    # mutable
    # set
    set_a = {1, 2, 3}
    # Two methods of shallow copy
    # set_b = set(set_a)
    set_b = copy(set_a)
    print('----- set')
    print("id of a:{} , id of b: {}".format(id(set_a), id(set_b)))
    # id of a:2099885540520 , id of b: 2099888490984
    print(set_a == set_b)  # True
    print(set_a is set_b)  # False
    # list
    list_a = [1, 2, 3]
    # Three methods of shallow copy
    # list_b = list_a[:]
    # list_b = list(list_b)
    list_b = copy(list_a)
    print('----- list')
    print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
    # id of a:2099888478280 , id of b: 2099888478472
    print(list_a == list_b)  # True
    print(list_a is list_b)  # False
    # Python小白學習交流群:711312441
    # dict
    dict_a = {"name": "xxx", "age": "123"}
    # Two methods of shallow copy
    # dict_b = dict(dict_a)
    dict_b = copy(dict_a)
    print('----- dict')
    print("id of a:{} , id of b: {}".format(id(dict_a), id(dict_b)))
    # id of a:2099855880480 , id of b: 2099886881024
    print(dict_a == dict_b)  # True
    print(dict_a is dict_b)  # False

這里有一點需要注意,對于 string 和 number 來說,正如上面提到的 Cpython 做了相應的優(yōu)化,讓不同的變量指向了相同的內(nèi)存地址,進而 id 的值是相等的。

但對于元組這個 immutable 元素來說,執(zhí)行 淺拷貝時,也不會創(chuàng)建一個內(nèi)存區(qū)域,只是返回一個老元組的引用。

對于其他的 mutable 對象,在淺拷貝后都會創(chuàng)建一個新的內(nèi)存區(qū)域,包含了被拷貝元素的引用。

淺拷貝正如它的名字那樣,當拷貝嵌套的 mutable 元素時,就會出現(xiàn)問題:

def shallow_copy_change_value():
    # list
    list_a = [1, 2, 3, [4, 5, 6]]
    list_b = copy(list_a)
    list_a[0] = 10
    list_a[3].append(7)
    print('----- list')
    print("ia:{} ,b: {}".format(list_a, list_b))
    print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
    # a:[10, 2, 3, [4, 5, 6, 7]] ,b: [1, 2, 3, [4, 5, 6, 7]]
    # id of a:1698595158472 , id of b: 1698595159752
    print(list_a == list_b)  # False
    print(list_a is list_b)  # False

下面是對上面 list 淺拷貝的圖解:

執(zhí)行淺拷貝操作:

72a62de32295d24a41f15aecb2f65eac

在 list_b 執(zhí)行淺拷貝后,創(chuàng)建一個新的對象,新對象中的 list_a[0] 指向 1.

修改 list_a 操作:

f82e60e92a6c04ab94146965ee4e985e

當執(zhí)行 list_a[0] = 10 操作時,由于 list_a[0] 本身是 number 類型,會重新創(chuàng)建一塊區(qū)域,用于保存新的值 10. 而新創(chuàng)建的 list_b[0] 并不會受到影響,還會指向之前的內(nèi)存區(qū)域。

當修改list_a[3] 操作時,由于list_a[3] 在淺拷貝后,新創(chuàng)建的對象中不會 嵌套創(chuàng)建 一個新的 list_a[3] 對象,僅僅是指向了之前的 list_a[3] 對象。所以當修改 list_a[3] 時, list_b[3] 也會收到影響。

9.深拷貝

對于深拷貝操作來說,除了會創(chuàng)建一個新的對象外,會還遞歸的遍歷老對象的中的嵌套元素,并形成新的副本。

def shallow_deepcopy_change_value():
    # list
    list_a = [1, 2, 3, [4, 5, 6]]
    list_b = deepcopy(list_a)
    list_a[0] = 10
    list_a[3].append(7)
    print('----- list')
    print("a:{} ,b: {}".format(list_a, list_b))
    print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
    # id of a:2099888478280 , id of b: 2099888478472
    print(list_a == list_b)  # False
    print(list_a is list_b)  # False

下面是對應圖解過程:

執(zhí)行深拷貝操作:

4a050f660c31f509ecde59eb8b589c38

修改 list_a 操作:

cd944895e4e8cb41278113baa1906af9

這里 list_a 和 list_b 已經(jīng)是完全的不同的兩個對象。

總結(jié)

在這篇文章中,主要介紹了 Python 中對象,以及對象的拷貝過程,主要有下面幾個重要的內(nèi)容:

  • Python 中變量沒有類型,僅僅可看做一個指針,通過引用指向?qū)ο?。變量可以刪除,但對象不行。
  • Python 對象被創(chuàng)建后,會擁有 identity,type 和 value 三個屬性。
  • immutable 和 mutable,主要在于 value 在其生命周期內(nèi)是否能發(fā)生變化。
  • 修改 mutable 對象時,所有指向它的變量都會受到影響。修改 immutable 對象時,指向它的其他變量沒有影響。
  • immutable 的大多數(shù)對象都是 hashable,但要考慮 immutable containers 的特殊情況。
  • 淺拷貝會創(chuàng)建一個新的內(nèi)存區(qū)域(對象),但其內(nèi)部是對原對象內(nèi)部引用的拷貝,在使用 mutable 對象時,存在一定的風險。
  • 深拷貝不但會創(chuàng)建一個新的內(nèi)存區(qū)域(對象),還會遞歸的創(chuàng)建原對象的所有嵌套對象,但也帶來了一些效率的問題。

以上就是Python 對象拷貝及深淺拷貝區(qū)別的詳細教程示例的詳細內(nèi)容,更多關于Python 對象拷貝的資料請關注腳本之家其它相關文章!

相關文章

最新評論