python自定义长度数组为什么这么自作主张?

jzl19901027
  • 180
a = [[],[],[]]
b = [[]] * 3

a[1].append(5)
b[1].append(5)

print(a)
print(b)

#输出
[[],[5],[]]
[[5],[5],[5]]

为什么用 []乘以常数 的方式自定义长度数组会出现这样的问题?
难道这种方式定义的数组里的所有元素共用一个指针或者是互相引用?

回复
阅读 6.1k
7 个回答

图片描述

如图,*的作用是相当于复制,b中的元素都指向的是同一个地址。这是从图中可以看出的,但是,我也希望找到相关文档可以参考一下原理。

a 的赋值相当于:
a1 = []
a2 = []
a3 = []
a = [a1, a2, a3]

b 的赋值相当于:
b1 = []
b = [b1,b1,b1]

是得,所有元素都是由b[0]复制而来,它们和b[0]是同一个对象

可以用b = [[] for _ in range(3)]

类似于浅拷贝, [[]] * 3 列表里面的对象只是多了一次引用而已,指向的同一个对象,如下:

In [1]: a = []

In [2]: import sys

In [3]: sys.getrefcount(a) # 计算引用数目
Out[3]: 2

In [4]: s = [a] * 3

In [5]: sys.getrefcount(a) 
Out[5]: 5

In [6]: map(id, s)   #内存地址都一样。
Out[6]: [140654381750824, 140654381750824, 140654381750824]

这个涉及到 mutable(可改)与 immutable(不可改) 的问题。
python 对象中除了字符串,数字,元组为 immutable 以外,其他均为 mutable。
对于 mutable(可改的) 的对象在复制时,实际上复制其指针(引用)。并没有重新创建一个对象。
而 imutable(不可改) 的对象在复制时,才会重新创建一个新的值。

b = [[]] * 3

这段代码实际上对同一个 [] 空列表对象复制了三次。
b[1].append(5) 虽然只改变了元素1,而元素2,元素0 是和元素1是相同的元素。

Python 一切皆对象,而任何对象都具有 ID(值的地址), 类型, 值 这三个属性:

  1. 对于 c = [[]] ,c 内相当于只生成了一个对象,也即 c[0] ,因此 c[0] 只会有一个值([None])的 ID.

  2. 对于 d = [[], []] ,d 内相当于生成了两个对象d[0], d[1],至于两个对象值是否相等,对于列表这样的可变对象 python 解释器都不会去管,而会一视同仁的分别分配一个值 ID 给同值对象,因此 d 内的两个对象其实分别有一个值的ID.

  3. 对于b = [[]]*3 这样的操作,*号只管一字不差的复制 b 内元素b[0],而非b[0][0],并生成两个新对象b[1]、b[2],至于要不要给b[1]、b[2]开辟等同于b[0][0]值的ID,*号根本不管;因为那是b[0]的属性,而不是b的。因此,b = [[]]*3虽然复制出三个独立的b内对象b[0]\b[1]\b[2],但后两个对象的值的引用其实都是照抄b[0]的,因此完全指向同一个值 ID.(相当于浅复制)。

再回到你的问题:

a = [[], [], []]
# 这种赋值方式,a 内的三个对象是相互独立,值也是各自独立互不干扰的;
b = [[]]*3 
# 这种赋值方式,b内元素相互独立,但三个对象值的 ID 是完全一样的;
b[1].append(5)
# 其会改写 b[1] 对应的 ID 下的值,因此会影响到 b[0],b[2]
a[1].append(5)
# 由于 a[1] 的ID 未与其他兄弟元素共享,因此不会影响 a[0],a[2]
你知道吗?

宣传栏