——Python在嵌套列表的子列表中append元素的问题
a = [[]] * 3
a[1].append('x')
b = [[] for _ in range(3)]
b[1].append('x')
a
和b
有何不同?有的同学可能会觉得这两种方法的结果是一样的,都是:
[[],['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])
我们可以看到a
、b
是可变对象,而c
是不可变容器对象。我们改变了a
的值,可以看到a
的编号没有发生改变,这个很正常,因为a
是可变对象。
同时因为c
中的元素包括a
,所以c
的“值”也发生了变化,但是c
的编号没有变化,也就是说还是那个不可变对象。也就是说c
的不变性是基于它的对象集没有变化。
网友评论