说到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,并进行身份比较。结果如下




python字节数组复制 python复制一个数组_Python 中list中所有值加和


哎,它俩id不一样,身份比较也判定为False,看样子列表切片实现了对原列表的复制?别急,我们继续看。


python字节数组复制 python复制一个数组_Python 中list中所有值加和_02


得,L[0]L2[0]是同一个对象。如果你试试其他的对位元素,结果也是一样的。

回过头来看L2 = L[:]这步操作,虽然创建了一个新的列表,但是其元素仍然与原列表元素的引用源一致,所以这种复制永远只是个浅复制,它创建了新的容器,但完全没有对元素对象做任何拷贝。当然它比L3 = L这种语句还是要强一些,这种连容器都没有拷贝,属于直接照搬的。

话说如此解释,难不成我们对L2的更改可以影响L?实验一下


python字节数组复制 python复制一个数组_arrays中copyof复制两个数组_03


没有发生影响呀!我前在扯淡?别急,我们先反思一下,L2[2] = 1是对L2[2]所指元素的修改吗?非也,必须指出,Python中的等号并不像很多静态语言一样是所谓“赋值”“修改”的含义,它是替换,是将一个引用指到一个新对象上去!

Python中的任何变量名,都不具有固定的数据类型,不占据固定的内存,而都只是对内存中对象的引用。而Python内置序列比如列表,则是利用一个容器,将多个这样的引用封装在了一起。所以,L2[2] = 1只是将L2[2]指向了一个新对象,并未对原对象进行任何修改(当然这里整数对象是immutable也修改不了,但我们可以重新实验,将L[2]预置为其他可变的mutable对象 ,重复类似上面的过程,在下面你会看到,结果是一样的)。

基于这样的前提,就可以推导出,如果容器内是immutable对象,这些对象原则上就不能原地修改,即使切片只是浅复制,修改切片产生的新容器内的元素,也不会影响另一个(因为本质是产生新对象加以替换);即使是mutable对象,如果执行的还是粗暴的替换操作,也不会有影响。除非你原地修改该mutable对象,才会在另一容器中显示出修改的效果。模拟实验结果如下


python字节数组复制 python复制一个数组_Python 中list中所有值加和_04


总结起来,Python中的标识符或者名称,都是对对象的引用;而列表中的元素,同样只是对实际对象的引用,列表里并不盛装实际的对象!Python列表的切片,复制的也只是这些引用,至于是否影响原列表,则取决于你是否对它引用的对象进行了所谓原地修改(一是要能修改,二是你执行的确实是原地操作)。