python 深拷貝和淺拷貝之可變和不可變對象總結

來源:互聯網
上載者:User

標籤:內容   允許   計數器   obj   false   new   參數   __name__   地址   

瞭解深拷貝和淺拷貝之前先要理解可變與不可變對象

python只允許使用引用傳遞,有可變對象和不可變對象,可變對象:list,dict.
不可變對象有:int,string,float,tuple

Python int,string,float,tuple不可變
舉栗子:

def int_object():
i = 89
j = 89
print(id(89))
print(‘i id:‘ + str(id(i)))
print(‘j id:‘ + str(id(j)))
print(i is j)
j = j + 1
print(‘new i id:‘ + str(id(i)))
print(‘new j id:‘ + str(id(j)))
print(i is j)
if __name__ == ‘__main__‘:
int_object()

運行結果
out:?
1715485840
i id:1715485840
j id:1715485840
True
new i id:1715485840
new j id:1715485856
False
有i和j倆個變數的值為89,通過列印89的ID和變數i,j在記憶體中的id我們得知它們都是指向同一塊
記憶體。所以說i和j都是指向同一個對象的。然後我們修改j的值,讓j的值+1.按道理j修改之後應該i
的值也發生改變的,因為它們都是指向的同一塊記憶體,但結果是並沒有。因為int類型是不可變類型,
所有其實是j複製了一份到新的記憶體位址然後+1,然後j又指向了新的地址。所以j的記憶體id發生了變化。

python list,dict可變對象

舉栗子

def dict_object():
a = {}
b = a
print(id(a))
a[‘a‘] = ‘hhh‘
print(‘id a:‘ + str(id(a)))
print(‘a:‘ +str(a))
print(‘id b:‘ + str(id(b)))
print(‘b:‘ + str(b))
if __name__ == ‘__main__‘:
dict_object()

運行結果

65616352
id a:65616352
a:{‘a‘: ‘hhh‘}
id b:65616352
b:{‘a‘: ‘hhh‘}

可以看到a最早的記憶體位址id是65616352 然後把a賦值給b其實就是讓變數b的也指向a所指向的記憶體
空間。然後我們發現當a發生變化後,b也跟著發生變化了,因為list是可變類型,所以並不會複製
一份再改變,而是直接在a所指向的記憶體空間修改資料,而b也是指向該記憶體空間的,自然b也就跟著
改變了。


python函數的參數傳遞

由於python規定參數傳遞都是傳遞引用,也就是傳遞給函數的是原變數實際所指向的記憶體空間,
修改的時候就會根據該引用的指向去修改該記憶體中的內容,所以按道理說我們在函數內改變了傳遞
過來的參數的值的話,原來外部的變數也應該受到影響。但是上面我們說到了python中有可變類型和
不可變類型,這樣的話,當傳過來的是可變類型(list,dict)時,我們在函數內部修改就會影響函數
外部的變數。而傳入的是不可變類型時在函數內部修改改變數並不會影響函數外部的變數,
因為修改的時候會先複製一份再修改。


舉栗子:

def fun1(a_int, b_list):
a_int = a_int + 1
b_list.append(‘6‘)
print(‘inner a_int:‘ + str(a_int))
print(‘inner b_list:‘ + str(b_list))

if __name__ == ‘__main__‘:
a_int = 8
b_list = [10, 11]

fun1(a_int, b_list)

print(‘outer a_int:‘ + str(a_int))
print(‘outer b_list:‘ + str(b_list))

輸出結果

inner a_int:9
inner b_list:[10, 11, ‘6‘]
outer a_int:8
outer b_list:[10, 11, ‘6‘]

經過fun1()方法修改後,傳遞過來的int類型外部變數沒有發生改變,而list這種可變類型則因為
fun1()方法的影響導致內容發生了改變。

總結:
python只允許引用傳遞是為方便記憶體管理,因為python使用的記憶體回收機制是計數器回收,
就是每塊記憶體上有一個計數器,表示當前有多少個對象指向該記憶體。每當一個變數不再使用時,
就讓該計數器-1,有新對象指向該記憶體時就讓計數器+1,當計時器為0時,就可以收回這塊記憶體了。
當然它肯定不止用了計數器吧,應該還有其他的技術,比如分代回收什麼的等


理解可變與不可變對象之後,現在說淺拷貝與深拷貝

淺拷貝僅僅複製了容器中元素的地址
舉栗子:

a = [‘hello‘,[1,2,3]]
b = a[:]
[id(x) for x in a]
[62493024, 62128416]
[id(x) for x in b]
[62493024, 61778560]
a[0]=‘world‘
a[1].append(4)
print(a)
[‘world‘, [1, 2, 3, 4]]
print(b)
[‘hello‘, [1, 2, 3, 4]]


a和b中元素的地址都是相同的,不可變的hello
和可變的list地址都一樣,說明淺拷貝只是將容器內的元素的地址複製了一份。這可以通過修改後,
b中字串沒改變,但是list元素隨著a相應改變得到驗證。

淺拷貝是在另一塊地址中建立一個新的變數或容器,但是容器內的元素的地址均是來源物件的元素的
地址的拷貝。也就是說新的容器中指向了舊的元素。

(二)
深拷貝,完全拷貝了一個副本,容器內部元素地址都不一樣
舉栗子:

from copy import deepcopy

a = [‘hello‘,[1,2,3]]
b = deepcopy(a)
[id(x) for x in a]
out: [62493024, 61752504]
[id(x) for x in b]
out: [62493024, 62131936]
a[0] = ‘Name‘
a[1].append(7)
print(a)
[‘Name‘, [1, 2, 4, 7]]
print(b)
[‘hello‘, [1, 2, 4]]

深拷貝後,a和b的地址以及a和b中的元素地址均不同,這是完全拷貝的一個副本,修改a後,
發現b沒有發生任何改變,因為b是一個完全的副本,元素地址與a均不同,a修改不影響b。
深拷貝是在另一塊地址中建立一個新的變數或容器,同時容器內的元素的地址也是新開闢的,
僅僅是值相同而已,是完全的副本

python 深拷貝和淺拷貝之可變和不可變對象總結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.