2020-2-10 Python 列表切片陷阱:引用、复制与深复制

Python 列表的切片和赋值操作很基础,之前也遇到过一些坑,以为自己很懂了。但今天刷 Codewars 时发现了一个更大的坑,故在此记录。

Python 列表赋值:复制“值”还是“引用”?

很多入门 Python 的人会犯这样一个错误:在赋值操作=中搞不清是赋了“值”还是“引用”。比如:

  1. a = [1, 2, 3]
  2. b = a
  3. b[0] = 10 # 更改列表 b 的第一个元素,但 a 现在也被更改为了 [10, 2, 3]

他可能只想改变列表b,但实际上这样也会改变列表a

因为b实际上是列表a的另一个引用ab是同一个对象,id(a) == id(b),所以更改b也会更改a。这个应该大部分人都知道。所以正确的代码应该使用切片来进行列表的复制

  1. a = [1, 2, 3]
  2. b = a[:] # 使用切片进行列表复制
  3. b[0] = 10 # 此时 a 和 b 是两个不同的对象

二维列表引发的思考:列表的本质

好的,现在我们确定切片能够进行列表的复制。那我们就能心安理得地改动新的列表了吗?请看二维列表(二维数组):

  1. a = [[1, 2, 3], [4, 5, 6]]
  2. b = a[:]
  3. b[0][0] = 10

此时,a还是被改动了!

原因是,虽然id(a) == id(b)Falseab确实不是同一个对象。但它们的元素都是同一个对象——id(a[0]) == id(b[0])id(a[1]) == id(b[1])。因为列表里存储的是对象的引用!

列表 list 终究只是个容器。就像 tuple 本身是 immutable (不可变)的,但它只是容器,它可以存储一个可变对象,因此呈现出一种可以被改动的“假象”。例如:

  1. >>> a = ([1],)
  2. >>> a[0][0] = 2
  3. >>> a
  4. ([2],)

所以容器和它存储的对象不能混为一谈。所以对于这种二维列表,想要进行完全的复制,请直接使用copy.deepcopy()深度复制。

如果只想复制一部分(切片),那可以先复制再切片:

  1. >>> import copy
  2. >>> a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  3. >>> b = copy.deepcopy(a)[1:]
  4. >>> b[0][0] = 100
  5. >>> a
  6. [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  7. >>> b
  8. [[100, 5, 6], [7, 8, 9]]

此时修改b没有影响到a

原文链接:http://www.cnblogs.com/gscnblog/p/10372539.html

原文地址:https://www.cnblogs.com/reclusive/p/12292251.html