Python深度拷贝也不是完美的

Python七号

共 1998字,需浏览 4分钟

 ·

2020-10-02 22:00

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的id y = 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>>>


以上都是我从极客时间「Python核心技术与实战」上学习到的,如果你也希望学习到高质量的 Python 技术,欢迎扫下方二维码订阅,并添加我微信「somenzz」好友,所得返现红包发你,不赚钱,交个朋友。
浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报