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

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

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

0

522

0

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

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

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

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

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

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

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

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

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

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

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

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

在下面的示例中,我們將創(chuàng)建一個(gè)新的嵌套列表,然后使用list()工廠函數(shù)對其進(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ú)立對象,其內(nèi)容與xs相同。您可以通過檢查兩個(gè)對象來驗(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ú)立于原始對象,讓我們設(shè)計(jì)一個(gè)小實(shí)驗(yàn)。您可以嘗試將新的子列表添加到原始列表(xs),然后檢查以確保此修改不會影響副本(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]]

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

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

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

>>> 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]]

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

如果我們在第一步中創(chuàng)建了xs的深副本,兩個(gè)對象將完全獨(dú)立。這就是對象的淺復(fù)制和深復(fù)制之間的實(shí)際區(qū)別。

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

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

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

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

制作深度復(fù)制

讓我們重復(fù)前面的列表復(fù)制示例,但有一個(gè)重要區(qū)別。這次我們將使用copy模塊中定義的deepcopy()函數(shù)來創(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í),您將看到它們看起來又是一樣的上一個(gè)示例:

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

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

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

>>> 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í)間坐下來使用Python解釋器并播放這些示例。當(dāng)你開始親身體驗(yàn)和體驗(yàn)這些例子時(shí),你可以更輕松地將你的頭腦圍繞在復(fù)制對象上。

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

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

復(fù)制任意Python對象

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

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

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

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})'

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

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

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

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

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

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

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

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

讓我們轉(zhuǎn)到一個(gè)更復(fù)雜的例子。我將定義另一個(gè)類來表示二維矩形。我將用一種方法來創(chuàng)建一個(gè)更復(fù)雜的對象層次結(jié)構(gòu)我的矩形將使用Point對象來表示它們的坐標(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__()重寫工作得多么順利,淺復(fù)制過程也按預(yù)期工作:

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

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

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

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

>>> 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)。

我們在這里討論了很多問題,復(fù)制對象還有一些更精細(xì)的地方。

深入是值得的(哈!)在本主題中,您可能需要學(xué)習(xí)copy模塊文檔。例如,對象可以通過在其上定義特殊方法__copy__()__deepcopy__()來控制它們的復(fù)制方式。

記住的3件事

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

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

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