源代码: Lib / copy.py
Python中的赋值语句不复制对象,它们在目标和对象之间创建绑定。对于可变或包含可变项的集合,有时需要一个副本,它不会影响另一个副本。该模块提供通用的浅层和深层拷贝操作(如下所述)。
返回x的浅拷贝。
返回x的深拷贝。
针对特定模块而引发的错误。
**浅复制和深复制之间的区别仅与复合对象(包含其他对象的对象,如列表或类实例)相关:
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]
也不会跟着改变。
深拷贝操作通常存在两个问题(浅拷贝操作不存在):
deepcopy()函数可以通过以下方式避免上述问题:
在当前复制过程中,使用一个memo字典保存已经复制的对象的字典,并且让用户定义的类重写拷贝操作或the set of components copied
。
此模块不复制模块,方法,堆栈跟踪,堆栈帧,文件,套接字,窗口,数组或任何类似类型。它通过返回原始对象来“复制”函数和类(浅和深); 这与pickle模块处理这些方式兼容。
字典的浅拷贝可以使用dict.copy()
方法,列表的浅拷贝可以使用整个列表的切片来完成,例如copied_list = original_list[:]
。
类可以使用相同的接口来控制用于控制 pickling的复制。有关pickle这些方法的信息,请参阅pickle模块的说明。实际上,该copy模块使用copyreg模块中注册的pickle函数。
为了让类定义自己的copy方法,它可以定义特殊的方法__copy__()
和__deepcopy__()
。前者被称为实现浅拷贝操作; 不接受参数。调用后者来实现深拷贝操作; 它传递了一个参数,memo字典。如果__deepcopy__()
实现需要对component
进行深层复制,则deepcopy()应该以component为第一个参数并将memo字典作为第二个参数。
也可以看看
模快 pickle
讨论用于支持对象状态检索和恢复的特殊方法。
译者注:
本实例节选自 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