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