Python 3.7.1 模組 資料型別 淺和深拷貝操作 copy
目錄
原始碼: Lib / copy.py
1. 需求描述
Python中的賦值語句不復制物件,它們在目標和物件之間建立繫結。對於可變或包含可變項的集合,有時需要一個副本,它不會影響另一個副本。該模組提供通用的淺層和深層拷貝操作(如下所述)。
2. 模組方法
copy.copy(x)
返回x的淺拷貝。
copy.deepcopy(x[, memo])
返回x的深拷貝。
exception copy.error
針對特定模組而引發的錯誤。
3. 區別
**淺複製和深複製之間的區別僅與複合物件(包含其他物件的物件,如列表或類例項)相關:
- 淺拷貝構造新的複合物件,然後(在可能的範圍)在原複合物件中找到的物件上插入引用。
- 深拷貝構造新的複合物件,然後,遞迴地,在原複合物件中找到的物件上插入拷貝。
3.1 普通例項
def test():
alist = [ _ for _ in range(3)]
adict ={"name":"leng","age":24}
aset = set(alist)
b = [99,'abc',alist,adict,aset]
d = copy.copy(b)
e = copy.deepcopy(b)
print("b={} id(b)={}\nd={} id(d)={}\ne={} id(e)={}".format(b,id(b),d,id(d),e,id(e)))
b[0],alist[0],b[3]['name']=999,-1,"lengfengyuyu"
aset. pop()
print("修改數值".center(50,"-"))
print("b={} \nd={} \ne={} ".format(b, d, e,))
print("記憶體地址".center(50, "-"))
print("id(b[0])={},id(b[2])={},id(b[3])={}".format(id(b[0]),id(b[2]),id(b[3])))
print("id(d[0])={},id(d[2])={},id(d[3])={}".format(id(d[0]), id(d[2]), id(d[3])))
print("id(e[0])={},id(e[2])={},id(e[3])={}".format(id(e[0]), id(e[2]), id(e[3])))
#輸出結果
b=[99, 'abc', [0, 1, 2], {'name': 'leng', 'age': 24}, {0, 1, 2}] id(b)=2674170588744
d=[99, 'abc', [0, 1, 2], {'name': 'leng', 'age': 24}, {0, 1, 2}] id(d)=2674171076488
e=[99, 'abc', [0, 1, 2], {'name': 'leng', 'age': 24}, {0, 1, 2}] id(e)=2674171074888
-----------------------修改數值-----------------------
b=[999, 'abc', [-1, 1, 2], {'name': 'lengfengyuyu', 'age': 24}, {1, 2}]
d=[99, 'abc', [-1, 1, 2], {'name': 'lengfengyuyu', 'age': 24}, {1, 2}]
e=[99, 'abc', [0, 1, 2], {'name': 'leng', 'age': 24}, {0, 1, 2}]
-----------------------記憶體地址-----------------------
id(b[0])=2674141091312,id(b[2])=2674170373320,id(b[3])=2674141330168
id(d[0])=140725287251856,id(d[2])=2674170373320,id(d[3])=2674141330168
id(e[0])=140725287251856,id(e[2])=2674171202760,id(e[3])=2674141330888
分析:
(1)b中的資料有很多,99
和'abc'
都是不可變的型別,深淺拷貝後和原資料互不影響,你變我不管。
(2)b中的alist,adict,aset
都是可變項的集合(也就是複合物件),淺拷貝後依然引用原資料(從記憶體地址相同可以看出),你變我也變;深拷貝後和原數資料互不影響(從記憶體地址不同同可以看出),你變我不管。
(3)例子中的d[0]
和e[0]
都指向的是99
,分配了相同的記憶體地址,但因為99
不是複合型別,因此就算d[0]
發生變化,e[0]
也不會跟著改變。
3.2 深拷貝注意事項
深拷貝操作通常存在兩個問題(淺拷貝操作不存在):
- 遞迴物件(直接或間接包含對自身的引用的複合物件)可能會導致遞迴迴圈。
- 因為深拷貝會複製它可能複製的所有內容,所以可能會在副本之間造成資料共享。
deepcopy()函式可以通過以下方式避免上述問題:
在當前複製過程中,使用一個memo字典儲存已經複製的物件的字典,並且讓使用者定義的類重寫拷貝操作或the set of components copied
。
4. 其它
此模組不復制模組,方法,堆疊跟蹤,堆疊幀,檔案,套接字,視窗,陣列或任何類似型別。它通過返回原始物件來“複製”函式和類(淺和深); 這與pickle模組處理這些方式相容。
字典的淺拷貝可以使用dict.copy()
方法,列表的淺拷貝可以使用整個列表的切片來完成,例如copied_list = original_list[:]
。
類可以使用相同的介面來控制用於控制 pickling的複製。有關pickle這些方法的資訊,請參閱pickle模組的說明。實際上,該copy模組使用copyreg模組中註冊的pickle函式。
為了讓類定義自己的copy方法,它可以定義特殊的方法__copy__()
和__deepcopy__()
。前者被稱為實現淺拷貝操作; 不接受引數。呼叫後者來實現深拷貝操作; 它傳遞了一個引數,memo字典。如果__deepcopy__()
實現需要對component
進行深層複製,則deepcopy()應該以component為第一個引數並將memo字典作為第二個引數。
也可以看看
模快 pickle
討論用於支援物件狀態檢索和恢復的特殊方法。
4.1 高階例項
譯者注:
本例項節選自 sqlmap 專案的AttribDict類,看一下類中是如何定義__deepcopy__()
的:
import copy
import types
class AttribDict(dict):
"""
This class defines the sqlmap object, inheriting from Python data
type dictionary.
>>> foo = AttribDict()
>>> foo.bar = 1
>>> foo.bar
1
"""
def __init__(self, indict=None, attribute=None):
if indict is None:
indict = {}
# Set any attributes here - before initialisation
# these remain as normal attributes
self.attribute = attribute
dict.__init__(self, indict)
self.__initialised = True
# 中間的程式碼省略
def __deepcopy__(self, memo):
retVal = self.__class__()
memo[id(self)] = retVal
for attr in dir(self):
if not attr.startswith('_'):
value = getattr(self, attr)
if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)):
setattr(retVal, attr, copy.deepcopy(value, memo))
for key, value in self.items():
retVal.__setitem__(key, copy.deepcopy(value, memo))
return retVal