Python?對(duì)象拷貝及深淺拷貝區(qū)別的詳細(xì)教程示例
Python 對(duì)象拷貝
在本篇文章中,會(huì)先介紹 Python 中對(duì)象的基礎(chǔ)概念,之后會(huì)提到對(duì)象的深淺拷貝以及區(qū)別。在閱讀后,應(yīng)該掌握如下的內(nèi)容:
- 理解變量、引用和對(duì)象的關(guān)系
- 理解 Python 對(duì)象中 identity,type 和 value 的概念
- 什么是 mutable 和 immutable 對(duì)象?以及它們和 hashable 的關(guān)系
- 深淺拷貝的過程以及區(qū)別
1.變量,引用和對(duì)象
變量無類型,它的作用僅僅在某個(gè)時(shí)候引用了特定的對(duì)象而已,具體在內(nèi)存中就是一個(gè)指針,僅僅擁有指向?qū)ο蟮目臻g大小。
變量和對(duì)象的關(guān)系在于引用,變量引用對(duì)象后,也就對(duì)應(yīng)了賦值的過程。
在 python 中一切皆為對(duì)象,具體在內(nèi)存中表示一塊內(nèi)存空間,每一個(gè)對(duì)象都會(huì)具有 identity,type 和 value 這三個(gè)內(nèi)容。
Identity, 一旦對(duì)象被創(chuàng)建后,Identity 的值便不會(huì)發(fā)生改變。在 Cpython 中,其值體現(xiàn)為內(nèi)存中保存對(duì)象的地址。is 操作符,比較對(duì)象是否相等就是通過這個(gè)值。通過 id() 函數(shù)查看它的整數(shù)形式。
Type, 和 Identity 一樣,在對(duì)象創(chuàng)建后,Type 也不會(huì)發(fā)生變化。它主要定義了一些可能支持的值和操作(如對(duì)列表來說,會(huì)有求長(zhǎng)度的操作)。通過 type() 函數(shù)可以得到對(duì)象的類型。
Value,用于表示的某些對(duì)象的值。當(dāng)對(duì)象在創(chuàng)建后值可以改變稱為 mutable,否則的話被稱為 immutable.
舉個(gè)例子,比如在 C 中,int x = 4 在內(nèi)存中,是先分配了一個(gè) int 類型的內(nèi)存空間,然后把 4 放進(jìn)空間內(nèi)。
而 Python 中,x = 4 正好相反,是為 4 分配了一塊的內(nèi)存空間,然后用 x 指向它。由于變量可以指向各種類型的對(duì)象,因此不需要像 C 一樣聲明變量。這也就是 Python 被稱為動(dòng)態(tài)類型的意義。
并且在 Python 中,變量可以刪除,但對(duì)象是無法刪除的。
2.immutable 和 mutable 對(duì)象
immutable 對(duì)象擁有一個(gè)固定的值,包括 numbers, strings, tuples. 一個(gè)新的值被保存時(shí),一個(gè)新的對(duì)象就會(huì)被創(chuàng)建。這些對(duì)象在作為常量的 hash 值中有著非常重要的作用,如作為字典的 key 時(shí)。
mutable 對(duì)象可以改變自身的值,但 id() 并不會(huì)發(fā)生改變。
當(dāng)一些對(duì)象包含對(duì)其他對(duì)象的一些引用時(shí),我們稱這些對(duì)象為 containers, 例如 list, tuple, dictionary 這些都是 containers. 這里需要注意的是,一個(gè) immutable containers 可以包含對(duì) mutable 對(duì)象的引用(如在 tuple 中包含一個(gè) list)。 但這個(gè)對(duì)象仍然稱為 immutable 對(duì)象,因?yàn)?Identity 是不變的。
3.hashable 對(duì)象
當(dāng)一個(gè)對(duì)象在生命周期內(nèi)(實(shí)現(xiàn)了 __hash__()
方法)hash 值不會(huì)發(fā)生改變,并可以與其他對(duì)象進(jìn)行比較(實(shí)現(xiàn)了 __eq__()
方法),稱之為hashable 對(duì)象。
在 Python 內(nèi)置的 immutable 對(duì)象 大多數(shù)都是 hashable 對(duì)象。immutable containers(tuples, frozenset)在引用的對(duì)象都是 hashable 對(duì)象時(shí),才是hashable 對(duì)象。mutable containers 容器都不是 hashable 對(duì)象。用戶自定義的類都是 hashable 對(duì)象,
4.淺拷貝與深拷貝
在介紹對(duì)象的拷貝前,先介紹一下 Python 中的賦值操作,可以讓我們更好的了解拷貝的過程。
5.賦值操作
賦值操作的右邊是簡(jiǎn)單表達(dá)式:
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() 反映了對(duì)象在內(nèi)存中的地址。可以看到,對(duì)于 immutable 對(duì)象中的 number 和 string 來說,CPython 本身對(duì)其做了一定的優(yōu)化,在創(chuàng)建相同的內(nèi)容時(shí),使其 指向了相同的內(nèi)存地址,從而被復(fù)用。
但是,Python 不會(huì)對(duì)所有 mutable 對(duì)象執(zhí)行此操作,因?yàn)閷?shí)現(xiàn)此功能需要一定的運(yùn)行時(shí)成本。對(duì)于在內(nèi)存中的對(duì)象來說,必須首先在內(nèi)存中搜索對(duì)象(搜索意味著時(shí)間)。對(duì)于 number 和 string 來說,搜索到它們很容易,所以才對(duì)其做了這樣的優(yōu)化。
對(duì)于其他類型的對(duì)象,雖然創(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
而當(dāng)賦值操作的右邊是已經(jīng)存在的 Python 對(duì)象時(shí),不論是什么類型的對(duì)象,都沒有在內(nèi)存中創(chuàng)建新的內(nèi)容,僅僅是聲明了一個(gè)新的變量指向之前內(nèi)存中已經(jīng)創(chuàng)建的對(duì)象,就像提供了一個(gè)別名一樣。
>>> 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操作,讓兩個(gè)變量同時(shí)指向了同一塊內(nèi)存區(qū)域。自然 id 相等。
當(dāng)對(duì) dict_b 重新賦值時(shí),僅讓 b 指向了另外一塊內(nèi)存區(qū)域,并不會(huì)影響 a 的指向,由于兩塊內(nèi)存區(qū)域不同,自然id 并不想等。
7.改變賦值后的對(duì)象
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
當(dāng)修改 imutable 對(duì)象時(shí),由于其本身不可改變,只能在內(nèi)存中新申請(qǐng)一塊新的空間,用于存儲(chǔ)修改后的內(nèi)容。對(duì)應(yīng)上面 a=20 的操作,這時(shí)再判斷 a 和 b 時(shí),由于指向了內(nèi)存的不同位置,所以 a,b不在相等。a 原來指向的內(nèi)存區(qū)域不會(huì)被回收,因?yàn)楝F(xiàn)在由 b 指向??梢钥吹?b 指向的內(nèi)存地址和 a 之前的指向的內(nèi)存地址是一致的。
當(dāng)修改 mutable 對(duì)象時(shí),由于都指向相同的內(nèi)存地址,所以對(duì)變量 list_a 修改的操作,也會(huì)映射到變量 list_b。
總結(jié)一下:
- 指向 imutable 的不同變量,當(dāng)其中一個(gè)變量被修改時(shí),其他變量不受影響,因?yàn)楸恍薷暮蟮淖兞繒?huì)指向一個(gè)新創(chuàng)建的對(duì)象。
- 指向 mutable 對(duì)象的不同變量,當(dāng)其中一個(gè)變量修改這個(gè)對(duì)象時(shí),會(huì)影響到指向這個(gè)對(duì)象的所有變量。
8.淺拷貝
淺拷貝創(chuàng)建了一個(gè)對(duì)象,這個(gè)對(duì)象包含了對(duì)被拷貝元素的參考。 所以當(dāng)使用淺拷貝來復(fù)制 conainters 對(duì)象時(shí),僅僅拷貝了那些嵌套元素的引用。
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小白學(xué)習(xí)交流群: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
這里有一點(diǎn)需要注意,對(duì)于 string 和 number 來說,正如上面提到的 Cpython 做了相應(yīng)的優(yōu)化,讓不同的變量指向了相同的內(nèi)存地址,進(jìn)而 id 的值是相等的。
但對(duì)于元組這個(gè) immutable 元素來說,執(zhí)行 淺拷貝時(shí),也不會(huì)創(chuàng)建一個(gè)內(nèi)存區(qū)域,只是返回一個(gè)老元組的引用。
對(duì)于其他的 mutable 對(duì)象,在淺拷貝后都會(huì)創(chuàng)建一個(gè)新的內(nèi)存區(qū)域,包含了被拷貝元素的引用。
淺拷貝正如它的名字那樣,當(dāng)拷貝嵌套的 mutable 元素時(shí),就會(huì)出現(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
下面是對(duì)上面 list 淺拷貝的圖解:
執(zhí)行淺拷貝操作:
72a62de32295d24a41f15aecb2f65eac
在 list_b 執(zhí)行淺拷貝后,創(chuàng)建一個(gè)新的對(duì)象,新對(duì)象中的 list_a[0] 指向 1.
修改 list_a 操作:
f82e60e92a6c04ab94146965ee4e985e
當(dāng)執(zhí)行 list_a[0] = 10 操作時(shí),由于 list_a[0] 本身是 number 類型,會(huì)重新創(chuàng)建一塊區(qū)域,用于保存新的值 10. 而新創(chuàng)建的 list_b[0] 并不會(huì)受到影響,還會(huì)指向之前的內(nèi)存區(qū)域。
當(dāng)修改list_a[3] 操作時(shí),由于list_a[3] 在淺拷貝后,新創(chuàng)建的對(duì)象中不會(huì) 嵌套創(chuàng)建 一個(gè)新的 list_a[3] 對(duì)象,僅僅是指向了之前的 list_a[3] 對(duì)象。所以當(dāng)修改 list_a[3] 時(shí), list_b[3] 也會(huì)收到影響。
9.深拷貝
對(duì)于深拷貝操作來說,除了會(huì)創(chuàng)建一個(gè)新的對(duì)象外,會(huì)還遞歸的遍歷老對(duì)象的中的嵌套元素,并形成新的副本。
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
下面是對(duì)應(yīng)圖解過程:
執(zhí)行深拷貝操作:
4a050f660c31f509ecde59eb8b589c38
修改 list_a 操作:
cd944895e4e8cb41278113baa1906af9
這里 list_a 和 list_b 已經(jīng)是完全的不同的兩個(gè)對(duì)象。
總結(jié)
在這篇文章中,主要介紹了 Python 中對(duì)象,以及對(duì)象的拷貝過程,主要有下面幾個(gè)重要的內(nèi)容:
- Python 中變量沒有類型,僅僅可看做一個(gè)指針,通過引用指向?qū)ο?。變量可以刪除,但對(duì)象不行。
- Python 對(duì)象被創(chuàng)建后,會(huì)擁有 identity,type 和 value 三個(gè)屬性。
- immutable 和 mutable,主要在于 value 在其生命周期內(nèi)是否能發(fā)生變化。
- 修改 mutable 對(duì)象時(shí),所有指向它的變量都會(huì)受到影響。修改 immutable 對(duì)象時(shí),指向它的其他變量沒有影響。
- immutable 的大多數(shù)對(duì)象都是 hashable,但要考慮 immutable containers 的特殊情況。
- 淺拷貝會(huì)創(chuàng)建一個(gè)新的內(nèi)存區(qū)域(對(duì)象),但其內(nèi)部是對(duì)原對(duì)象內(nèi)部引用的拷貝,在使用 mutable 對(duì)象時(shí),存在一定的風(fēng)險(xiǎn)。
- 深拷貝不但會(huì)創(chuàng)建一個(gè)新的內(nèi)存區(qū)域(對(duì)象),還會(huì)遞歸的創(chuàng)建原對(duì)象的所有嵌套對(duì)象,但也帶來了一些效率的問題。
以上就是Python 對(duì)象拷貝及深淺拷貝區(qū)別的詳細(xì)教程示例的詳細(xì)內(nèi)容,更多關(guān)于Python 對(duì)象拷貝的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python實(shí)現(xiàn)將HTML轉(zhuǎn)成PDF的方法分析
這篇文章主要介紹了Python實(shí)現(xiàn)將HTML轉(zhuǎn)成PDF的方法,結(jié)合實(shí)例形式分析了Python基于pdfkit模塊實(shí)現(xiàn)HTML轉(zhuǎn)換成PDF文件的相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-05-05三步實(shí)現(xiàn)Django Paginator分頁(yè)的方法
這篇文章主要介紹了三步實(shí)現(xiàn)Django Paginator分頁(yè)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06python實(shí)現(xiàn)的用于搜索文件并進(jìn)行內(nèi)容替換的類實(shí)例
這篇文章主要介紹了python實(shí)現(xiàn)的用于搜索文件并進(jìn)行內(nèi)容替換的類,涉及Python針對(duì)文件及字符串的相關(guān)操作技巧,需要的朋友可以參考下2015-06-06python生成隨機(jī)驗(yàn)證碼(中文驗(yàn)證碼)示例
這篇文章主要介紹了python生成中文隨機(jī)驗(yàn)證碼示例,需要的朋友可以參考下2014-04-04python實(shí)現(xiàn)切割url得到域名、協(xié)議、主機(jī)名等各個(gè)字段的例子
今天小編就為大家分享一篇python實(shí)現(xiàn)切割url得到域名、協(xié)議、主機(jī)名等各個(gè)字段的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-07-07解決Pytorch修改預(yù)訓(xùn)練模型時(shí)遇到key不匹配的情況
這篇文章主要介紹了解決Pytorch修改預(yù)訓(xùn)練模型時(shí)遇到key不匹配的情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06