<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在本篇文章中,會先介紹 Python 中物件的基礎概念,之後會提到物件的深淺拷貝以及區別。在閱讀後,應該掌握如下的內容:
變數無型別,它的作用僅僅在某個時候參照了特定的物件而已,具體在記憶體中就是一個指標,僅僅擁有指向物件的空間大小。
變數和物件的關係在於參照,變數參照物件後,也就對應了賦值的過程。
在 python 中一切皆為物件,具體在記憶體中表示一塊記憶體空間,每一個物件都會具有 identity,type 和 value 這三個內容。
Identity, 一旦物件被建立後,Identity 的值便不會發生改變。在 Cpython 中,其值體現為記憶體中儲存物件的地址。is 操作符,比較物件是否相等就是通過這個值。通過 id() 函數檢視它的整數形式。
Type, 和 Identity 一樣,在物件建立後,Type 也不會發生變化。它主要定義了一些可能支援的值和操作(如對列表來說,會有求長度的操作)。通過 type() 函數可以得到物件的型別。
Value,用於表示的某些物件的值。當物件在建立後值可以改變稱為 mutable,否則的話被稱為 immutable.
舉個例子,比如在 C 中,int x = 4 在記憶體中,是先分配了一個 int 型別的記憶體空間,然後把 4 放進空間內。
而 Python 中,x = 4 正好相反,是為 4 分配了一塊的記憶體空間,然後用 x 指向它。由於變數可以指向各種型別的物件,因此不需要像 C 一樣宣告變數。這也就是 Python 被稱為動態型別的意義。
並且在 Python 中,變數可以刪除,但物件是無法刪除的。
immutable 物件擁有一個固定的值,包括 numbers, strings, tuples. 一個新的值被儲存時,一個新的物件就會被建立。這些物件在作為常數的 hash 值中有著非常重要的作用,如作為字典的 key 時。
mutable 物件可以改變自身的值,但 id() 並不會發生改變。
當一些物件包含對其他物件的一些參照時,我們稱這些物件為 containers, 例如 list, tuple, dictionary 這些都是 containers. 這裡需要注意的是,一個 immutable containers 可以包含對 mutable 物件的參照(如在 tuple 中包含一個 list)。 但這個物件仍然稱為 immutable 物件,因為 Identity 是不變的。
當一個物件在生命週期內(實現了 __hash__()
方法)hash 值不會發生改變,並可以與其他物件進行比較(實現了 __eq__()
方法),稱之為hashable 物件。
在 Python 內建的 immutable 物件 大多數都是 hashable 物件。immutable containers(tuples, frozenset)在參照的物件都是 hashable 物件時,才是hashable 物件。mutable containers 容器都不是 hashable 物件。使用者自定義的類都是 hashable 物件,
在介紹物件的拷貝前,先介紹一下 Python 中的賦值操作,可以讓我們更好的瞭解拷貝的過程。
賦值操作的右邊是簡單表示式:
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() 反映了物件在記憶體中的地址。可以看到,對於 immutable 物件中的 number 和 string 來說,CPython 本身對其做了一定的優化,在建立相同的內容時,使其 指向了相同的記憶體地址,從而被複用。
但是,Python 不會對所有 mutable 物件執行此操作,因為實現此功能需要一定的執行時成本。對於在記憶體中的物件來說,必須首先在記憶體中搜尋物件(搜尋意味著時間)。對於 number 和 string 來說,搜尋到它們很容易,所以才對其做了這樣的優化。
對於其他型別的物件,雖然建立的內容相同,但都在記憶體中完全建立了一塊新的區域。
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
而當賦值操作的右邊是已經存在的 Python 物件時,不論是什麼型別的物件,都沒有在記憶體中建立新的內容,僅僅是宣告了一個新的變數指向之前記憶體中已經建立的物件,就像提供了一個別名一樣。
>>> 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操作,讓兩個變數同時指向了同一塊記憶體區域。自然 id 相等。
當對 dict_b 重新賦值時,僅讓 b 指向了另外一塊記憶體區域,並不會影響 a 的指向,由於兩塊記憶體區域不同,自然id 並不想等。
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 物件時,由於其本身不可改變,只能在記憶體中新申請一塊新的空間,用於儲存修改後的內容。對應上面 a=20 的操作,這時再判斷 a 和 b 時,由於指向了記憶體的不同位置,所以 a,b不在相等。a 原來指向的記憶體區域不會被回收,因為現在由 b 指向。可以看到 b 指向的記憶體地址和 a 之前的指向的記憶體地址是一致的。
當修改 mutable 物件時,由於都指向相同的記憶體地址,所以對變數 list_a 修改的操作,也會對映到變數 list_b。
總結一下:
淺拷貝建立了一個物件,這個物件包含了對被拷貝元素的參考。 所以當使用淺拷貝來複制 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 做了相應的優化,讓不同的變數指向了相同的記憶體地址,進而 id 的值是相等的。
但對於元組這個 immutable 元素來說,執行 淺拷貝時,也不會建立一個記憶體區域,只是返回一個老元組的參照。
對於其他的 mutable 物件,在淺拷貝後都會建立一個新的記憶體區域,包含了被拷貝元素的參照。
淺拷貝正如它的名字那樣,當拷貝巢狀的 mutable 元素時,就會出現問題:
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 淺拷貝的圖解:
執行淺拷貝操作:
72a62de32295d24a41f15aecb2f65eac
在 list_b 執行淺拷貝後,建立一個新的物件,新物件中的 list_a[0] 指向 1.
修改 list_a 操作:
f82e60e92a6c04ab94146965ee4e985e
當執行 list_a[0] = 10 操作時,由於 list_a[0] 本身是 number 型別,會重新建立一塊區域,用於儲存新的值 10. 而新建立的 list_b[0] 並不會受到影響,還會指向之前的記憶體區域。
當修改list_a[3] 操作時,由於list_a[3] 在淺拷貝後,新建立的物件中不會 巢狀建立 一個新的 list_a[3] 物件,僅僅是指向了之前的 list_a[3] 物件。所以當修改 list_a[3] 時, list_b[3] 也會收到影響。
對於深拷貝操作來說,除了會建立一個新的物件外,會還遞迴的遍歷老物件的中的巢狀元素,並形成新的副本。
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
下面是對應圖解過程:
執行深拷貝操作:
4a050f660c31f509ecde59eb8b589c38
修改 list_a 操作:
cd944895e4e8cb41278113baa1906af9
這裡 list_a 和 list_b 已經是完全的不同的兩個物件。
在這篇文章中,主要介紹了 Python 中物件,以及物件的拷貝過程,主要有下面幾個重要的內容:
以上就是Python 物件拷貝及深淺拷貝區別的詳細教學範例的詳細內容,更多關於Python 物件拷貝的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45