中文字幕一区二区人妻电影,亚洲av无码一区二区乱子伦as ,亚洲精品无码永久在线观看,亚洲成aⅴ人片久青草影院按摩,亚洲黑人巨大videos

Python對(duì)象的淺復(fù)制與深復(fù)制

發(fā)布于:2021-02-01 14:28:20

0

510

0

python 對(duì)象 淺復(fù)制 深復(fù)制

Python中的賦值語(yǔ)句不創(chuàng)建對(duì)象的副本,它們只將名稱(chēng)綁定到對(duì)象。對(duì)于不可變對(duì)象,這通常沒(méi)有什么區(qū)別。

但是對(duì)于處理可變對(duì)象或可變對(duì)象的集合,您可能正在尋找一種方法來(lái)創(chuàng)建這些對(duì)象的“真實(shí)副本”或“克隆”。

本質(zhì)上,您有時(shí)需要可以修改的副本,而無(wú)需同時(shí)自動(dòng)修改原始副本。在本文中,我將向您簡(jiǎn)要介紹如何在python3中復(fù)制或“克隆”對(duì)象,以及其中涉及的一些注意事項(xiàng)。

注意:本教程是用Python3編寫(xiě)的,但在復(fù)制對(duì)象方面,Python2和Python3沒(méi)有什么區(qū)別。如果有差異,我會(huì)在文中指出。

讓我們先看看如何復(fù)制Python的內(nèi)置集合。Python內(nèi)置的可變集合(如list、dict和set)可以通過(guò)在現(xiàn)有集合上調(diào)用它們的工廠函數(shù)來(lái)復(fù)制:

new_list = list(original_list)
new_dict = dict(original_dict)
new_set = set(original_set)

但是,這種方法不適用于自定義對(duì)象,除此之外,它只創(chuàng)建淺復(fù)制。對(duì)于列表、dict和set等復(fù)合對(duì)象,淺復(fù)制和深復(fù)制有一個(gè)重要區(qū)別:

  • 淺復(fù)制意味著構(gòu)造一個(gè)新的集合對(duì)象,然后用在原始集合中找到的子對(duì)象的引用填充它。從本質(zhì)上說(shuō),淺復(fù)制只是一個(gè)層次的深復(fù)制。復(fù)制過(guò)程不會(huì)遞歸,因此不會(huì)創(chuàng)建子對(duì)象本身的副本。

  • 深度復(fù)制使復(fù)制過(guò)程遞歸。這意味著首先構(gòu)造一個(gè)新的集合對(duì)象,然后用在原始集合中找到的子對(duì)象的副本遞歸地填充它。以這種方式復(fù)制對(duì)象會(huì)遍歷整個(gè)對(duì)象樹(shù),以創(chuàng)建原始對(duì)象及其所有子對(duì)象的完全獨(dú)立克隆。

我知道,那有點(diǎn)過(guò)分了。所以讓我們看一些例子來(lái)說(shuō)明深復(fù)制和淺復(fù)制之間的區(qū)別。

進(jìn)行淺復(fù)制

在下面的示例中,我們將創(chuàng)建一個(gè)新的嵌套列表,然后使用list()工廠函數(shù)對(duì)其進(jìn)行淺復(fù)制:

>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys = list(xs)  # Make a shallow copy

這意味著ys現(xiàn)在將是一個(gè)新的獨(dú)立對(duì)象,其內(nèi)容與xs相同。您可以通過(guò)檢查兩個(gè)對(duì)象來(lái)驗(yàn)證這一點(diǎn):

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

要確認(rèn)ys確實(shí)獨(dú)立于原始對(duì)象,讓我們?cè)O(shè)計(jì)一個(gè)小實(shí)驗(yàn)。您可以嘗試將新的子列表添加到原始列表(xs),然后檢查以確保此修改不會(huì)影響副本(ys):

>>> xs.append(['new sublist'])
>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9], ['new sublist']]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

如您所見(jiàn),這達(dá)到了預(yù)期的效果。在“淺表”級(jí)別修改復(fù)制的列表完全沒(méi)有問(wèn)題。

但是,由于我們只創(chuàng)建了原始列表的淺表副本,ys仍然包含對(duì)存儲(chǔ)在xs中的原始子對(duì)象的引用這些子對(duì)象沒(méi)有被復(fù)制。它們只是在復(fù)制的列表中再次引用。

因此,當(dāng)您修改xs中的一個(gè)子對(duì)象時(shí),這種修改也會(huì)反映在ys中,這是因?yàn)閮蓚€(gè)列表共享相同的子對(duì)象。副本只是一個(gè)淺的、一級(jí)的深副本:

>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9], ['new sublist']]
>>> ys
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]

在上面的示例中,我們(似乎)只對(duì)xs進(jìn)行了更改。但事實(shí)證明,在xsys中,索引1處的兩個(gè)子列表都被修改了。再次發(fā)生這種情況是因?yàn)槲覀冎粍?chuàng)建了原始列表的淺副本。

如果我們?cè)诘谝徊街袆?chuàng)建了xs的深副本,兩個(gè)對(duì)象將完全獨(dú)立。這就是對(duì)象的淺復(fù)制和深復(fù)制之間的實(shí)際區(qū)別。

現(xiàn)在您知道如何創(chuàng)建一些內(nèi)置集合類(lèi)的淺復(fù)制,并且知道了淺復(fù)制和深復(fù)制之間的區(qū)別。我們?nèi)匀恍枰卮鸬膯?wèn)題是:

  • 如何創(chuàng)建內(nèi)置集合的深度副本?

  • 如何創(chuàng)建任意對(duì)象(包括自定義類(lèi))的副本(淺副本和深副本)?

這些問(wèn)題的答案在Python標(biāo)準(zhǔn)庫(kù)的copy模塊中。此模塊提供了一個(gè)簡(jiǎn)單的界面,用于創(chuàng)建任意Python對(duì)象的淺復(fù)制和深復(fù)制。

制作深度復(fù)制

讓我們重復(fù)前面的列表復(fù)制示例,但有一個(gè)重要區(qū)別。這次我們將使用copy模塊中定義的deepcopy()函數(shù)來(lái)創(chuàng)建深度副本:

>>> import copy
>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> zs = copy.deepcopy(xs)

當(dāng)您檢查xs及其用copy.deepcopy()創(chuàng)建的克隆zs時(shí),您將看到它們看起來(lái)又是一樣的上一個(gè)示例:

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> zs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

但是,如果您對(duì)原始對(duì)象(xs)中的一個(gè)子對(duì)象進(jìn)行修改,您將看到此修改不會(huì)影響深度副本(zs)。

這一次,原始對(duì)象和副本都是完全獨(dú)立的。xs是遞歸克隆的,包括它的所有子對(duì)象:

>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
>>> zs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

您現(xiàn)在可能需要花一些時(shí)間坐下來(lái)使用Python解釋器并播放這些示例。當(dāng)你開(kāi)始親身體驗(yàn)和體驗(yàn)這些例子時(shí),你可以更輕松地將你的頭腦圍繞在復(fù)制對(duì)象上。

順便說(shuō)一句,你也可以使用copy模塊中的函數(shù)創(chuàng)建淺復(fù)制。copy.copy()函數(shù)創(chuàng)建對(duì)象的淺復(fù)制。

如果您需要清楚地告知您正在代碼中的某個(gè)地方創(chuàng)建淺復(fù)制,這將非常有用。使用copy.copy()可以指示此事實(shí)。但是,對(duì)于內(nèi)置的集合,只需使用list、dict和set factory函數(shù)來(lái)創(chuàng)建淺層副本就被認(rèn)為更具Pythonic了。

復(fù)制任意Python對(duì)象

我們?nèi)匀恍枰卮鸬膯?wèn)題是如何創(chuàng)建任意對(duì)象(包括自定義類(lèi))的副本(淺副本和深副本)?,F(xiàn)在讓我們來(lái)看看。

再一次,copy模塊來(lái)拯救我們。它的copy.copy()copy.deepcopy()功能可用于復(fù)制任何對(duì)象。

再次強(qiáng)調(diào),了解如何使用這些功能的最佳方法是通過(guò)簡(jiǎn)單的實(shí)驗(yàn)。我將以前面的列表復(fù)制示例為基礎(chǔ)。讓我們從定義一個(gè)簡(jiǎn)單的2D點(diǎn)類(lèi)開(kāi)始:

class Point:
   def __init__(self, x, y):
       self.x = x
       self.y = y

   def __repr__(self):
       return f'Point({self.x!r}, {self.y!r})'

我希望您同意這非常簡(jiǎn)單。我添加了一個(gè)__repr__()實(shí)現(xiàn),這樣我們就可以在Python解釋器中輕松地檢查從這個(gè)類(lèi)創(chuàng)建的對(duì)象。

注意:上面的示例使用python3.6f-string來(lái)構(gòu)造__repr__返回的字符串。在Python2和Python3.6之前的版本上,您將使用不同的字符串格式表達(dá)式,例如:

def __repr__(self):
   return 'Point(%r, %r)' % (self.x, self.y)

接下來(lái),我們將創(chuàng)建一個(gè)Point實(shí)例,然后(粗略地)復(fù)制它,使用copy模塊:

>>> a = Point(23, 42)
>>> b = copy.copy(a)

如果我們檢查原始Point對(duì)象及其(淺層)克隆的內(nèi)容,我們會(huì)看到預(yù)期的結(jié)果:

>>> a
Point(23, 42)
>>> b
Point(23, 42)
>>> a is b
False

這里還有一些需要記住的內(nèi)容。因?yàn)槲覀兊狞c(diǎn)對(duì)象使用不可變類(lèi)型(int)作為其坐標(biāo),所以在這種情況下淺復(fù)制和深復(fù)制沒(méi)有區(qū)別。但我馬上就要展開(kāi)這個(gè)例子了。

讓我們轉(zhuǎn)到一個(gè)更復(fù)雜的例子。我將定義另一個(gè)類(lèi)來(lái)表示二維矩形。我將用一種方法來(lái)創(chuàng)建一個(gè)更復(fù)雜的對(duì)象層次結(jié)構(gòu)我的矩形將使用Point對(duì)象來(lái)表示它們的坐標(biāo):

class Rectangle:
   def __init__(self, topleft, bottomright):
       self.topleft = topleft
       self.bottomright = bottomright

   def __repr__(self):
       return (f'Rectangle({self.topleft!r}, '
               f'{self.bottomright!r})')

再次,首先我們將嘗試創(chuàng)建一個(gè)矩形實(shí)例的淺層副本:

rect = Rectangle(Point(0, 1), Point(5, 6))
srect = copy.copy(rect)

如果您檢查原始矩形及其副本,您將看到__repr__()重寫(xiě)工作得多么順利,淺復(fù)制過(guò)程也按預(yù)期工作:

>>> rect
Rectangle(Point(0, 1), Point(5, 6))
>>> srect
Rectangle(Point(0, 1), Point(5, 6))
>>> rect is srect
False

還記得前面的列表示例如何說(shuō)明深復(fù)制和淺復(fù)制之間的區(qū)別嗎?我要用同樣的方法。我將在對(duì)象層次結(jié)構(gòu)中更深層地修改一個(gè)對(duì)象,然后您將在(淺層)副本中看到這一更改:

>>> rect.topleft.x = 999
>>> rect
Rectangle(Point(999, 1), Point(5, 6))
>>> srect
Rectangle(Point(999, 1), Point(5, 6))

我希望這是您所期望的。接下來(lái),我將創(chuàng)建原始矩形的深度副本。然后我將應(yīng)用另一個(gè)修改,您將看到哪些對(duì)象受到影響:

>>> drect = copy.deepcopy(srect)
>>> drect.topleft.x = 222
>>> drect
Rectangle(Point(222, 1), Point(5, 6))
>>> rect
Rectangle(Point(999, 1), Point(5, 6))
>>> srect
Rectangle(Point(999, 1), Point(5, 6))

Voila!這一次,深復(fù)制(drect)完全獨(dú)立于原始拷貝(rect)和淺復(fù)制(srect)。

我們?cè)谶@里討論了很多問(wèn)題,復(fù)制對(duì)象還有一些更精細(xì)的地方。

深入是值得的(哈?。┰诒局黝}中,您可能需要學(xué)習(xí)copy模塊文檔。例如,對(duì)象可以通過(guò)在其上定義特殊方法__copy__()__deepcopy__()來(lái)控制它們的復(fù)制方式。

記住的3件事

  • 對(duì)對(duì)象進(jìn)行淺層復(fù)制不會(huì)克隆子對(duì)象。因此,副本并非完全獨(dú)立于原始對(duì)象。

  • 對(duì)象的深度副本將遞歸地克隆子對(duì)象??寺⊥耆?dú)立于原始副本,但創(chuàng)建深度副本的速度較慢。

  • 您可以使用copy模塊復(fù)制任意對(duì)象(包括自定義類(lèi))。