專欄地址:每週一個 Python 模塊html
copy
模塊包括兩個功能,copy()
和 deepcopy()
,用於複製現有對象。python
copy()
建立的淺表副本是一個新容器,是對原始對象內容的引用。git
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
a = MyClass('a')
my_list = [a]
dup = copy.copy(my_list)
print(' my_list:', my_list)
print(' dup:', dup)
print(' dup is my_list:', (dup is my_list))
print(' dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))
# output
# my_list: [<__main__.MyClass object at 0x101f9c160>]
# dup: [<__main__.MyClass object at 0x101f9c160>]
# dup is my_list: False
# dup == my_list: True
# dup[0] is my_list[0]: True
# dup[0] == my_list[0]: True
複製代碼
對於淺拷貝,MyClass
實例並不複製,所以dup
和 my_list
引用的是同一個對象。github
將調用替換爲 deepcopy()
會使輸出明顯不一樣。數據結構
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
a = MyClass('a')
my_list = [a]
dup = copy.deepcopy(my_list)
print(' my_list:', my_list)
print(' dup:', dup)
print(' dup is my_list:', (dup is my_list))
print(' dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))
# output
# my_list: [<__main__.MyClass object at 0x101e9c160>]
# dup: [<__main__.MyClass object at 0x1044e1f98>]
# dup is my_list: False
# dup == my_list: True
# dup[0] is my_list[0]: False
# dup[0] == my_list[0]: True
複製代碼
列表的第一個元素再也不是相同的對象引用,可是當比較兩個對象時,它們仍然是相等的。app
可使用 __copy__()
和__deepcopy__()
方法來自定義複製行爲。spa
__copy__()
不須要參數,返回該對象的淺拷貝副本。__deepcopy__()
使用 memo 字典調用,並返回該對象的深拷貝對象。任何須要深度複製的成員屬性,都應與 memo 字典一塊兒傳遞給 copy.deepcopy()
。如下示例說明了如何調用方法。code
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
def __copy__(self):
print('__copy__()')
return MyClass(self.name)
def __deepcopy__(self, memo):
print('__deepcopy__({})'.format(memo))
return MyClass(copy.deepcopy(self.name, memo))
a = MyClass('a')
sc = copy.copy(a)
dc = copy.deepcopy(a)
# output
# __copy__()
# __deepcopy__({})
複製代碼
memo 字典用於跟蹤已經複製的值,以免無限遞歸。orm
爲避免重複遞歸數據結構的問題,deepcopy()
使用字典來跟蹤已複製的對象。這個字典被傳遞給__deepcopy__()
方法,所以能夠在這裏檢查重複遞歸問題。cdn
下一個示例顯示了互連數據結構(若有向圖)如何經過實現__deepcopy__()
方法來防止遞歸。
import copy
class Graph:
def __init__(self, name, connections):
self.name = name
self.connections = connections
def add_connection(self, other):
self.connections.append(other)
def __repr__(self):
return 'Graph(name={}, id={})'.format(
self.name, id(self))
def __deepcopy__(self, memo):
print('\nCalling __deepcopy__ for {!r}'.format(self))
if self in memo:
existing = memo.get(self)
print(' Already copied to {!r}'.format(existing))
return existing
print(' Memo dictionary:')
if memo:
for k, v in memo.items():
print(' {}: {}'.format(k, v))
else:
print(' (empty)')
dup = Graph(copy.deepcopy(self.name, memo), [])
print(' Copying to new object {}'.format(dup))
memo[self] = dup
for c in self.connections:
dup.add_connection(copy.deepcopy(c, memo))
return dup
root = Graph('root', [])
a = Graph('a', [root])
b = Graph('b', [a, root])
root.add_connection(a)
root.add_connection(b)
dup = copy.deepcopy(root)
# output
# Calling __deepcopy__ for Graph(name=root, id=4326183824)
# Memo dictionary:
# (empty)
# Copying to new object Graph(name=root, id=4367233208)
#
# Calling __deepcopy__ for Graph(name=a, id=4326186344)
# Memo dictionary:
# Graph(name=root, id=4326183824): Graph(name=root, id=4367233208)
# Copying to new object Graph(name=a, id=4367234720)
#
# Calling __deepcopy__ for Graph(name=root, id=4326183824)
# Already copied to Graph(name=root, id=4367233208)
#
# Calling __deepcopy__ for Graph(name=b, id=4326183880)
# Memo dictionary:
# Graph(name=root, id=4326183824): Graph(name=root, id=4367233208)
# Graph(name=a, id=4326186344): Graph(name=a, id=4367234720)
# 4326183824: Graph(name=root, id=4367233208)
# 4367217936: [Graph(name=root, id=4326183824), Graph(name=a, id=4326186344)]
# 4326186344: Graph(name=a, id=4367234720)
# Copying to new object Graph(name=b, id=4367235000)
複製代碼
Graph
類包括幾個基本的有向圖的方法。可使用名稱和與其鏈接的現有節點列表初始化實例。add_connection()
方法用於設置雙向鏈接。它也被深拷貝操做符使用。
__deepcopy__()
方法打印消息以顯示其調用方式,並根據須要管理備忘錄字典內容。它不是複製整個鏈接列表,而是建立一個新列表,並將各個鏈接的副本添加進去。這確保了備忘錄字典在每一個新節點被複制時更新,而且它避免了遞歸問題或節點的額外副本。和之前同樣,該方法在完成後返回複製的對象。
具備循環的對象圖的深層複製
圖中顯示的圖形包括幾個週期,但使用備註字典處理遞歸可防止遍歷致使堆棧溢出錯誤。
第二次根遇到一個節點,而這個節點被複制,__deepcopy__()
檢測該遞歸和重用來自備忘錄字典現有值而不是建立新的對象。
相關文檔: