Python,明明只append了一次,为什么所有子列表都变了啊

2021-09-06  本文已影响0人  FelixZzzz
——Python在嵌套列表的子列表中append元素的问题
a = [[]] * 3
a[1].append('x')
b = [[] for _ in range(3)]
b[1].append('x')

ab有何不同?有的同学可能会觉得这两种方法的结果是一样的,都是:

[[],['x'],[]]

然而真实的结果是:

[['x'], ['x'], ['x']] # a
[[], ['x'], []] # b

为什么会产生不同的结果呢?明明只append了一次,为什么a中的所有子列表都变了呢?这要从Python的数据模型说起。

Python中每个对象都有各自的编号、类型和值。一个对象被创建后,它的编号就绝不会改变,你可以将其理解为该对象在内存中的地址。Python还提供了is运算符和id()函数用于比较和查看对象的编号。

is运算符可以比较两个对象的编号是否相同;id()函数能返回一个代表其编号的整型数。

Python中有些对象的值可以改变,有些不可以。值可以改变的对象被称为可变对象;值不可以改变的对象就被称为不可变对象。(一个不可变容器对象如果包含对可变对象的引用,当后者的值改变时,前者的值也会改变;但是该容器仍属于不可变对象,因为它所包含的对象集是不会改变的。因此,不可变并不严格等同于值不能改变,实际含义要更微妙。) 一个对象的可变性是由其类型决定的;例如,数字、字符串和元组是不可变的,而字典和列表是可变的。[1]

在一开始的问题中,列表a初始化的时候,内层的子列表[]实际上是引用了同一个可变对象。因此对内层的任意一个子列表的append()操作,最终会因为引用了同一个对象的原因,体现在每一个子列表上。

>>> id(a[0])
4525967488
>>> id(a[1])
4525967488
>>> id(a[2])
4525967488

b初始化的时候是创建了3个不同[]对象。每个子列表引用了不同的对象。

>>> id(b[0])
4525966272
>>> id(b[1])
4525924864
>>> id(b[2])
4525906304

Python的官方文档中也说明了这种情况,lst * n这种形式相当于lst与自身进行n次拼接。lst中的项不会被拷贝,而是会进行多次引用。[2]

我们再深入的看一下刚才括号里面很拗口的那句话:

“一个不可变容器对象如果包含对可变对象的引用,当后者的值改变时,前者的值也会改变;但是该容器仍属于不可变对象,因为它所包含的对象集是不会改变的。因此,不可变并不严格等同于值不能改变,实际含义要更微妙。”

简单做个实验:

>>> a = [1,2]
>>> b = [3,4]
>>> c = (a,b)
>>> c
([1, 2], [3, 4])

>>> id(a)
4302958976
>>> id(b)
4302958912
>>> id(c)
4302959488

>>> a[0] = 5
>>> a
[5, 2]
>>> id(a)
4302958976
>>> id(c)
4302959488
>>> c
([5, 2], [3, 4])

我们可以看到ab是可变对象,而c是不可变容器对象。我们改变了a的值,可以看到a的编号没有发生改变,这个很正常,因为a是可变对象。
同时因为c中的元素包括a,所以c的“值”也发生了变化,但是c的编号没有变化,也就是说还是那个不可变对象。也就是说c的不变性是基于它的对象集没有变化。


  1. https://docs.python.org/zh-cn/3/reference/datamodel.html#data-model

  2. https://docs.python.org/zh-cn/3/library/stdtypes.html#common-sequence-operations

上一篇 下一篇

猜你喜欢

热点阅读