Python 浅复制的特性还是缺陷?

概述

我们首先来看官方对列表/字典中copy方法的描述:

>>> help(dict.copy)
Help on method_descriptor:

copy(...)
    D.copy() -> a shallow copy of D

>>>
>>> help(list.copy)
Help on method_descriptor:

copy(...)
    L.copy() -> list -- a shallow copy of L

可以看到,官方对它们的描述都是对原有对象的浅复制(在上一篇文章中 Python——copy/deepcopy中我已说明了copy库中浅/深复制的概念,这里的浅复制概念同copy.copy,不再赘述)。

那么,我们创建一个dict_instance对象,再用copy_instance来接受对dict_instance浅复制的返回结果:

>>> dict_instance = {0: [0], 1: [0, 1], 2: [0, 1, 2]}
>>> copy_instance = dict_instance.copy()
>>> dict_instance[0][0] = 1
>>> dict_instance
{0: [1], 1: [0, 1], 2: [0, 1, 2]}
>>> copy_instance
{0: [1], 1: [0, 1], 2: [0, 1, 2]}

上述,我们修改了字典对象的“0”键所指代的列表值的第一个元素为1,即dict_instance[0][0] = 1,显然copy_instance跟随了这样的修改。可以自行使用id函数看到,确实copy实现了这样的浅复制。

缺陷还是特性?

到目前为止,copy都是正常工作,也符合我们的预期。从一开始,我便希望浅复制时刻保持地址的一致性,但python的浅复制(不论是copy.copy、list.copy还是dict.copy)是否能够时刻保持地址的一致性呢?

我们知道,在Python中,元组、int型、字符串等类型都是不可变类型,在上一篇文章中提到了我们在容器对象中对不可变类型的”修改“实际上是将它指向新的对象/地址,并不是原地修改

我从逻辑上希望浅复制时刻保持地址的一致性,忽略因”修改“不可变元素而破坏了一致性。但事实上:

>>> from copy import copy
>>> list_instance = [1, 2, 3]
>>> copy_instance = copy(list_instance)
>>> [id(x) for x in list_instance]
[1386622096, 1386622112, 1386622128]
>>> [id(x) for x in copy_instance]
[1386622096, 1386622112, 1386622128]
>>> list_instance[0] = 4
>>> list_instance
[4, 2, 3]
>>> copy_instance
[1, 2, 3]
>>> [id(x) for x in list_instance]
[1386622144, 1386622112, 1386622128]
>>> [id(x) for x in copy_instance]
[1386622096, 1386622112, 1386622128]

从上述代码可明显地观察到copy.copy方法并没有时刻保持地址的一致性,当我们”修改“不可变元素时就破坏了这一致性。同理我们观察list.copy和dict.copy:

>>> list_copy = list_instance.copy()
>>> list_instance
[4, 2, 3]
>>> list_copy
[4, 2, 3]
>>> [id(x) for x in list_instance]
[1386622144, 1386622112, 1386622128]
>>> [id(x) for x in list_copy]
[1386622144, 1386622112, 1386622128]
>>> list_instance[0] = 0
>>> list_instance
[0, 2, 3]
>>> list_copy
[4, 2, 3]

>>> dict_instance = {0: 0, 1: 1, 2: 2}
>>> dict_copy = dict_instance.copy()
>>> [id(x) for x in dict_instance]
[1386622080, 1386622096, 1386622112]
>>> [id(x) for x in dict_copy]
[1386622080, 1386622096, 1386622112]
>>> dict_instance[0] = 3
>>> dict_copy
{0: 0, 1: 1, 2: 2}

可以明显地观察到,列表和字典自带的copy方法与copy.copy表现地一致,均没有在”修改“不可变元素后继续保持一致

这与我的预期相悖:从我的逻辑上来说,既然是浅复制,且是Python这门贴近人类自然逻辑的语言,它们应当时刻保持地址一致性,对编程人员屏蔽所有破坏地址一致性的操作,强制保持一致。但事实结果已经在上文阐述了。

我无法说清这是否是这门语言的特性,但这确确实实会造成对代码理解的二义性,事实上我和我的朋友就在前几日编程时便错误解读了浅复制的这种特性,甚至因这种特性混淆了深浅复制的概念,于是我认为它应该是一种逻辑缺陷,但如果要弥补缺陷,又会造成新的问题诸如兼容性,为考虑这种情形需要添加的分支判断,修改原有运行机制等,这是一个值得思考的问题。


版权声明:本文为Christopher_L1n原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。