说到Python中数组的切片操作,稍有了解的想必都不陌生。以Python的内置数据类型
list
(列表)为例,
L = [5, 2, 0, 1, 3, 1, 4]
L1 = L[3:7]
L[3:7]
或者说L1
为列表L
的一个切片,它切取的当然就是L
中从3号位置到7号位置前的部分,也就是[1, 3, 1, 4]
,可形象化理解如下。
+---+---+---+---+---+---+---+
| 5 | 2 | 0 | 1 | 3 | 1 | 4 |
+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7
我们今天要探讨的是,在Python中对数组(比如列表)进行切片,是否会造成对数组的复制?
Python为何要将一套操作设计得如此繁复多变?岂不有违优雅的理念?
————————我是分割线————————
我们接下来在交互式的shell中进行一组实验,读者可与网络上其他类似文章对比一下。
定义的列表L
还是跟上面一样,这次切片选择整个L
,
L2 = L[:]
然后分别探测它们的id
,并进行身份比较。结果如下
哎,它俩id
不一样,身份比较也判定为False,看样子列表切片实现了对原列表的复制?别急,我们继续看。
得,L[0]
跟L2[0]
是同一个对象。如果你试试其他的对位元素,结果也是一样的。
回过头来看L2 = L[:]
这步操作,虽然创建了一个新的列表,但是其元素仍然与原列表元素的引用源一致,所以这种复制永远只是个浅复制,它创建了新的容器,但完全没有对元素对象做任何拷贝。当然它比L3 = L
这种语句还是要强一些,这种连容器都没有拷贝,属于直接照搬的。
话说如此解释,难不成我们对L2的更改可以影响L?实验一下
没有发生影响呀!我前在扯淡?别急,我们先反思一下,L2[2] = 1
是对L2[2]
所指元素的修改吗?非也,必须指出,Python中的等号并不像很多静态语言一样是所谓“赋值”“修改”的含义,它是替换,是将一个引用指到一个新对象上去!
Python中的任何变量名,都不具有固定的数据类型,不占据固定的内存,而都只是对内存中对象的引用。而Python内置序列比如列表,则是利用一个容器,将多个这样的引用封装在了一起。所以,L2[2] = 1
只是将L2[2]
指向了一个新对象,并未对原对象进行任何修改(当然这里整数对象是immutable也修改不了,但我们可以重新实验,将L[2]
预置为其他可变的mutable对象 ,重复类似上面的过程,在下面你会看到,结果是一样的)。
基于这样的前提,就可以推导出,如果容器内是immutable对象,这些对象原则上就不能原地修改,即使切片只是浅复制,修改切片产生的新容器内的元素,也不会影响另一个(因为本质是产生新对象加以替换);即使是mutable对象,如果执行的还是粗暴的替换操作,也不会有影响。除非你原地修改该mutable对象,才会在另一容器中显示出修改的效果。模拟实验结果如下
总结起来,Python中的标识符或者名称,都是对对象的引用;而列表中的元素,同样只是对实际对象的引用,列表里并不盛装实际的对象!Python列表的切片,复制的也只是这些引用,至于是否影响原列表,则取决于你是否对它引用的对象进行了所谓原地修改(一是要能修改,二是你执行的确实是原地操作)。