可悲的我一直以为copy模块是用C写的,有时候需要深入了解deepcopy,文档描述的实在太简单,还是不知所云。
比如说最近看sqlmap源码中AttribDict的_deepcopy__有些疑惑,
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.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这个memo是啥玩意,为啥要id(self),如果是统一都是id,直接传个self,copy内部做id的操作不是更简单。
第二个问题我们自己思考一下就可以解决,用id肯定是为了唯一识别,如果把self传过去,很危险。因为你压根不知道内部将对self做什么操作,也许你会疑惑是否对self本身有危害吗?这也符合传递参数最小原则。
copy.py开头的doc非常不错,简要翻译一下:
主要接口:
import copy x=copy.copy(y) #对y的浅拷贝 x=copy.deepcopy(y) #对y的深拷贝如果copy模块出错,将抛出copy.Error异常
浅复制和深复制的不同仅仅是对组合对象来说,所谓的组合对象就是包含了其它对象的对象,如列表,类实例。
--浅拷贝将构造一个新的组合对象,然后将所包含的对象直接插入到新的组合对象中
--深拷贝讲构造一个新的组合对象,然后递归的拷贝所包含的对象并插入到新的组合对象中
深拷贝存在两个浅拷贝没有的问题:
a) 递归拷贝对象(组合对象可能直接或间接的包含自己的引用)可能导致循环拷贝
b)由于深拷贝将拷贝所有,可能导致拷贝太多,如可能共享一些数据结构
Python通过下面方法解决上面的问题:
a) 保存一个已经拷贝的对象表
b) 允许用户在类中自定义拷贝操作
copy模块不拷贝如下类型:module,class ,function,method,stack trace,stack frame,file,socket,window,array或相似类型。
我们可以看看deepcopy是如何避免死循环的,
def deepcopy(x, memo=None, _nil=[]): """Deep copy operation on arbitrary Python objects. See the module‘s __doc__ string for more info. """ if memo is None: memo = {} d = id(x) y = memo.get(d, _nil) #查看是否已经拷贝,避免拷贝死循环 if y is not _nil: return y
正如我们所看到的,meno的确是以对象的id为key的,所以对文章开头所说的也就不足为奇了。
我们先看看浅拷贝的操作:
def _copy_immutable(x): #浅拷贝不可变对象,返回自己 return x for t in (type(None), int, long, float, bool, str, tuple, frozenset, type, xrange, types.ClassType, types.BuiltinFunctionType, type(Ellipsis), types.FunctionType, weakref.ref): d[t] = _copy_immutable def _copy_with_constructor(x): #拷贝可变对象,需要重新创建一个新的对象 return type(x)(x) # type(1)其实就是int for t in (list, dict, set): d[t] = _copy_with_constructor浅拷贝操作比较简单,根据需要拷贝的类型,调用对应的方法。唯一需要注意的就是type的用法。
再来看看深拷贝操作,需要重点关注tuple:
def _deepcopy_atomic(x, memo): #深拷贝区分是否可分,也就是是否是组合对象,和钱拷贝区分维度上有异 return x #对非组合对象(原子对象)就是返回自己 d[type(None)] = _deepcopy_atomic d[type(Ellipsis)] = _deepcopy_atomic d[int] = _deepcopy_atomic d[long] = _deepcopy_atomic d[float] = _deepcopy_atomic d[bool] = _deepcopy_atomic def _deepcopy_list(x, memo): """ 由于列表是可变对象,所以列表的深拷贝就是对所包含的所有元素进行深拷贝 可以和下面元组进行对比,更好的理解可变与不可变 """ y = [] memo[id(x)] = y for a in x: y.append(deepcopy(a, memo)) return y d[list] = _deepcopy_list def _deepcopy_tuple(x, memo): """ 因为元组是不可变对象,所以处理有些特殊 我们先看看元组的deepcopy 1.都是不可变对象 In [48]: t1=(1,(2,3),‘s‘) In [49]: t2=copy.deepcopy(t1) In [50]: id(t1),id(t2) #t1就是t2 Out[50]: (43829008, 43829008) 2.包含可变对象 In [51]: t1=(1,[2,3],‘s‘) In [52]: t2=copy.deepcopy(t1) In [53]: id(t1),id(t2) #t1和t2是不同的 Out[53]: (43828528, 43828968) """ y = [] for a in x: #?为什么不在位置1做这个操作 y.append(deepcopy(a, memo)) d = id(x) try: return memo[d] except KeyError: pass ##位置1 for i in range(len(x)): if x[i] is not y[i]: #不等也就是说该元组包含可变对象 y = tuple(y) #使用元组构造函数重新生成 break else: #没有break,说明该元组都是不可变对象,不需要重新生成 y = x memo[d] = y return y d[tuple] = _deepcopy_tuple def _deepcopy_dict(x, memo): y = {} memo[id(x)] = y for key, value in x.iteritems(): #字典key,value都需要深拷贝 y[deepcopy(key, memo)] = deepcopy(value, memo) return y d[dict] = _deepcopy_dict