對(duì)象拷貝
在面向?qū)ο缶幊讨猩氚瑁瑥?fù)制一個(gè)現(xiàn)有對(duì)象的副本搁凸,被稱(chēng)為對(duì)象拷貝媚值。生成的對(duì)象被稱(chēng)之為對(duì)象副本。
拷貝是基本的操作护糖,但是在實(shí)際操作過(guò)程中會(huì)有一些需要注意的地方褥芒。有多種方法來(lái)拷貝對(duì)象,最常見(jiàn)的是通過(guò)拷貝構(gòu)造函數(shù)或者克隆函數(shù)嫡良∶谭觯拷貝的主要目的是對(duì)副本做出修改、移動(dòng)寝受,或者是保留當(dāng)前值坷牛。
如果不需要實(shí)現(xiàn)以上目的,則創(chuàng)建對(duì)原始數(shù)據(jù)的引用是更加高效的選擇很澄。
Python的對(duì)象拷貝
在Python中京闰,賦值語(yǔ)句總是建立對(duì)象的引用,看下面的一段代碼
>>> a = [1,2,3]
>>> b = a
>>> print(id(a), id(b))
(4420030688, 4420030688)
會(huì)發(fā)現(xiàn)b = a
操作后痴怨,a
和b
指向了同一塊地址忙干。
上面例子中,a
和b
都是可變對(duì)象(list)的浪藻,對(duì)b
的修改捐迫,同樣可會(huì)影響到a
,如下
>>> b.append(3)
>>> a
[1, 2, 3, 3]
那么對(duì)于可變的數(shù)據(jù)的副本爱葵,或者包含了可變對(duì)象的數(shù)據(jù)的副本施戴,如何在不改變?cè)瓟?shù)據(jù)的情況下操作副本呢?這里就要用到內(nèi)置的copy
模塊了萌丈。
淺拷貝
在淺拷貝中赞哗,會(huì)將原對(duì)象中所有字段復(fù)制給對(duì)象副本。如果某個(gè)字段是某個(gè)對(duì)象的引用(比如辆雾,一串內(nèi)存地址)肪笋,那么被拷貝的是這個(gè)引用,指向元數(shù)據(jù)中該字段指向的對(duì)象度迂;如果字段是基本類(lèi)型藤乙,則復(fù)制這個(gè)字段的值。
在Python這樣萬(wàn)物皆是對(duì)象的語(yǔ)言中惭墓,使用淺拷貝后坛梁,對(duì)象副本中字段和原始數(shù)據(jù)中字段都會(huì)指向同一個(gè)對(duì)象,這樣的情況下腊凶,引用的對(duì)象被共享划咐,因此拴念,如果這些對(duì)象中的一個(gè)被修改,則改變?cè)诹硗庖粋€(gè)中可見(jiàn)褐缠。淺拷貝很簡(jiǎn)單政鼠,可以通過(guò)簡(jiǎn)單的完全復(fù)制位來(lái)實(shí)現(xiàn),所以開(kāi)銷(xiāo)也很小送丰。
比如下面的例子缔俄。
>>> a = [[1,2,3], [1,2], [1]]
>>> id(a), id(a[0]), id(a[1]), id(a[2])
(4420406880, 4420406448, 4420406808, 4420406304)
>>> b = copy.copy(a)
>>> id(b), id(b[0]), id(b[1]), id(b[2])
(4420423760, 4420406448, 4420406808, 4420406304)
深拷貝
另一種方法是深拷貝,這意味著原對(duì)象中的字段被解引用器躏。和淺拷貝不同俐载,引用不再被拷貝,而是會(huì)為引用的值創(chuàng)建新的對(duì)象登失。也就是說(shuō)對(duì)于副本的改變不會(huì)影響到原始數(shù)據(jù)遏佣。copy
模塊中的deepcopy
函數(shù)可以實(shí)現(xiàn)這一點(diǎn)。
>>> import copy
>>> l1 = [2, 3, 4, [3, 5]]
>>> l2 = copy.deepcopy(l1)
>>> l1, l2
([2, 3, 4, [3, 5]], [2, 3, 4, [3, 5]])
>>> l2[3].append(6)
>>> l2.append(7)
>>> l1, l2
([2, 3, 4, [3, 5]], [2, 3, 4, [3, 5, 6], 7])
總結(jié)
Python中copy
模塊通過(guò)copy()
和deepcopy()
函數(shù)來(lái)分別提供淺拷貝和深拷貝功能揽浙。程序員可以通過(guò)在對(duì)象中定義__copy__()
和__deepcopy()__
方法來(lái)修改模塊默認(rèn)的實(shí)現(xiàn)状婶。