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

Python對象的淺復制與深復制

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

0

499

0

python 對象 淺復制 深復制

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

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

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

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

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

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

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

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

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

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

進行淺復制

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

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

這意味著ys現(xiàn)在將是一個新的獨立對象,其內(nèi)容與xs相同。您可以通過檢查兩個對象來驗證這一點:

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

要確認ys確實獨立于原始對象,讓我們設計一個小實驗。您可以嘗試將新的子列表添加到原始列表(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]]

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

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

因此,當您修改xs中的一個子對象時,這種修改也會反映在ys中,這是因為兩個列表共享相同的子對象。副本只是一個淺的、一級的深副本:

>>> 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進行了更改。但事實證明,在xsys中,索引1處的兩個子列表都被修改了。再次發(fā)生這種情況是因為我們只創(chuàng)建了原始列表的淺副本。

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

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

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

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

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

制作深度復制

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

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

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

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

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

這一次,原始對象和副本都是完全獨立的。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)在可能需要花一些時間坐下來使用Python解釋器并播放這些示例。當你開始親身體驗和體驗這些例子時,你可以更輕松地將你的頭腦圍繞在復制對象上。

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

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

復制任意Python對象

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

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

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

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

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

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

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

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

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

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

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

這里還有一些需要記住的內(nèi)容。因為我們的點對象使用不可變類型(int)作為其坐標,所以在這種情況下淺復制和深復制沒有區(qū)別。但我馬上就要展開這個例子了。

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

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)建一個矩形實例的淺層副本:

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

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

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

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

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

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

>>> 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!這一次,深復制(drect)完全獨立于原始拷貝(rect)和淺復制(srect)。

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

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

記住的3件事

  • 對對象進行淺層復制不會克隆子對象。因此,副本并非完全獨立于原始對象。

  • 對象的深度副本將遞歸地克隆子對象。克隆完全獨立于原始副本,但創(chuàng)建深度副本的速度較慢。

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