Python深度拷贝也不是完美的
Python的浅拷贝和深拷贝的区别,相信你已经非常熟悉了,浅拷贝就是对原对象重新申请一个内存空间,但原对象的子对象如果是可变对象,仍然是存在引用关系的;深拷贝也是重新申请内存空间,以递归的方式,通过创建新的子对象拷贝到新对象中,无论是原对象还是其子对象都是彼此独立的,新对象和原对象没有任何关联。
不过,深度拷贝也不是完美的,先看段代码,你可以先预测下程序的输出,然后在执行下,看看预期是否一致。
import copyx = [1]x.append(x)y = copy.deepcopy(x)# 以下命令的输出是?x == y
程序执行到第 3 行时,x 已经是一个无限嵌套的列表,但是,执行到第 4 行时,程序进行了深度拷贝,就会递归的创建新的子对象,却并没有发生内存溢出的错误,这是为什么呢?
其实,这是因为深度拷贝函数 deepcopy 中会维护一个字典,记录已经拷贝的对象与其 ID。拷贝过程中,如果字典里已经存储了将要拷贝的对象,则会从字典直接返回,我们来看相对应的源码就能明白:
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) # 查询被拷贝对象x的idy = memo.get(d, _nil) # 查询字典里是否已经存储了该对象if y is not _nil:return y # 如果字典里已经存储了将要拷贝的对象,则直接返回...
程序执行到第 7 行比较两个对象的值是否相等时却会报错,这是什么原因呢?
因为 x 是一个无限嵌套的列表,y 深拷贝于 x,按道理来讲 x == y 应该是 True 的,但进行比较操作符 == 的时候,== 操作符则会递归地遍历对象的所有值,并逐一比较。而 Python 为了防止栈崩溃,递归的层数是要限定的,不会无休下去,所以到了限定的层数,Python 解释器会跳出错误:
> import copy> x=[1]> x.append(x)> x[1, [...]]> y = copy.deepcopy(x)> x == yTraceback (most recent call last):File "" , line 1, in <module>RecursionError: maximum recursion depth exceeded in comparison>
其原因也是 Python 的递归层数是有限定的,在 sys 模块中有个方法可以得到递归的层数:
> import sys> sys.getrecursionlimit()1000
当然你也可以重新设定递归的层数:
> import sys> sys.getrecursionlimit()1000> sys.setrecursionlimit(10000)> sys.getrecursionlimit()10000
那是不是可以设定无穷大呢?理论上可以,但你的程序崩溃也是一定的,你自己可以做个测试。
总结一下,深度拷贝的缺点在于如果对象内存在指向自身的引用,那么很容易出现无限循环,而引用、浅拷贝却不会,如下:
> import copy> x=[1]> x.append(x)> x_alias = x> x_copy = copy.copy(x)> x_deepcopy = copy.deepcopy(x)> x == x_aliasTrue> x == x_copyTrue> x == x_deepcopyTraceback (most recent call last):File "" , line 1, in <module>RecursionError: maximum recursion depth exceeded in comparison>
