Python之美[从菜鸟到高手]--浅拷贝、深拷贝完全解读(copy源码分析)

时间:2021-11-02 22:29:24

可悲的我一直以为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