我创建了一个列表的列表:

>>> xs = [[1] * 4] * 3
>>> print(xs)
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

然后,我改变了最里面的一个值:

>>> xs[0][0] = 5
>>> print(xs)
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

为什么每个子列表的第一个元素都变成了5?


参见:

我如何克隆一个列表,使它不会在分配后意外改变?寻找解决问题的方法

Python:对于字典列表的类似问题,字典列表只存储每次迭代中最后追加的值

如何初始化一个字典,其值是不同的空列表?对于列表字典的类似问题


当前回答

当你写[x]*3时,你会得到[x, x, x]这个列表。也就是说,一个有3个对同一个x的引用的列表。当你修改这个x时,它通过所有三个对它的引用都是可见的:

x = [1] * 4
xs = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(xs[0]): {id(xs[0])}\n"
    f"id(xs[1]): {id(xs[1])}\n"
    f"id(xs[2]): {id(xs[2])}"
)
# id(xs[0]): 140560897920048
# id(xs[1]): 140560897920048
# id(xs[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"xs: {xs}")
# xs: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

为了解决这个问题,您需要确保在每个位置都创建了一个新列表。一种方法是

[[1]*4 for _ in range(3)]

它将每次重新计算[1]*4,而不是计算一次并对1个列表进行3次引用。


You might wonder why * can't make independent objects the way the list comprehension does. That's because the multiplication operator * operates on objects, without seeing expressions. When you use * to multiply [[1] * 4] by 3, * only sees the 1-element list [[1] * 4] evaluates to, not the [[1] * 4 expression text. * has no idea how to make copies of that element, no idea how to reevaluate [[1] * 4], and no idea you even want copies, and in general, there might not even be a way to copy the element.

*的唯一选择是对现有子列表进行新的引用,而不是尝试创建新的子列表。其他任何东西都是不一致的,或者需要对基本语言设计决策进行重大的重新设计。

相比之下,列表推导式在每次迭代时重新计算元素表达式。[[1] * 4 for n in range(3)]每次重新计算[1]* 4,原因相同[x**2 for x in range(3)]每次重新计算x**2。每次对[1]* 4求值都会生成一个新列表,因此列表推导式执行您想要的操作。

顺便说一句,[1]* 4也不复制[1]的元素,但这没关系,因为整数是不可变的。你不能做类似于1的东西。Value = 2,把1变成2。

其他回答

[[1] * 4] * 3

甚至:

[[1, 1, 1, 1]] * 3

创建一个3次引用内部[1,1,1,1]的列表——而不是内部列表的3个副本,因此任何时候修改列表(在任何位置),您都会看到3次更改。

和下面这个例子一样:

>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

在那里可能不那么令人惊讶。

size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for _ in range(size)]

使用Python导师进行实时可视化:

当你写[x]*3时,你会得到[x, x, x]这个列表。也就是说,一个有3个对同一个x的引用的列表。当你修改这个x时,它通过所有三个对它的引用都是可见的:

x = [1] * 4
xs = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(xs[0]): {id(xs[0])}\n"
    f"id(xs[1]): {id(xs[1])}\n"
    f"id(xs[2]): {id(xs[2])}"
)
# id(xs[0]): 140560897920048
# id(xs[1]): 140560897920048
# id(xs[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"xs: {xs}")
# xs: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

为了解决这个问题,您需要确保在每个位置都创建了一个新列表。一种方法是

[[1]*4 for _ in range(3)]

它将每次重新计算[1]*4,而不是计算一次并对1个列表进行3次引用。


You might wonder why * can't make independent objects the way the list comprehension does. That's because the multiplication operator * operates on objects, without seeing expressions. When you use * to multiply [[1] * 4] by 3, * only sees the 1-element list [[1] * 4] evaluates to, not the [[1] * 4 expression text. * has no idea how to make copies of that element, no idea how to reevaluate [[1] * 4], and no idea you even want copies, and in general, there might not even be a way to copy the element.

*的唯一选择是对现有子列表进行新的引用,而不是尝试创建新的子列表。其他任何东西都是不一致的,或者需要对基本语言设计决策进行重大的重新设计。

相比之下,列表推导式在每次迭代时重新计算元素表达式。[[1] * 4 for n in range(3)]每次重新计算[1]* 4,原因相同[x**2 for x in range(3)]每次重新计算x**2。每次对[1]* 4求值都会生成一个新列表,因此列表推导式执行您想要的操作。

顺便说一句,[1]* 4也不复制[1]的元素,但这没关系,因为整数是不可变的。你不能做类似于1的东西。Value = 2,把1变成2。

简单地说,这是因为在python中,一切都是通过引用工作的,所以当你以这种方式创建一个列表的列表时,你基本上会遇到这样的问题。

为了解决你的问题,你可以做其中之一: 1. 使用numpy.empty的numpy数组文档 2. 当您到达一个列表时,请添加该列表。 3.如果你愿意,你也可以用字典

我来到这里是因为我想看看如何嵌套任意数量的列表。上面有很多解释和具体的例子,但是你可以概括出N维的列表的列表的列表的列表…用以下递归函数:

import copy

def list_ndim(dim, el=None, init=None):
    if init is None:
        init = el

    if len(dim)> 1:
        return list_ndim(dim[0:-1], None, [copy.copy(init) for x in range(dim[-1])])

    return [copy.deepcopy(init) for x in range(dim[0])]

第一次调用函数是这样的:

dim = (3,5,2)
el = 1.0
l = list_ndim(dim, el)

其中(3,5,2)是结构尺寸的元组(类似于numpy shape参数),1.0是你想要初始化结构的元素(也适用于None)。请注意,init参数仅由递归调用提供,用于向前携带嵌套的子列表

以上输出:

[[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
 [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
 [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]]

设置具体元素:

l[1][3][1] = 56
l[2][2][0] = 36.0+0.0j
l[0][1][0] = 'abc'

输出结果:

[[[1.0, 1.0], ['abc', 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
 [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 56.0], [1.0, 1.0]],
 [[1.0, 1.0], [1.0, 1.0], [(36+0j), 1.0], [1.0, 1.0], [1.0, 1.0]]]

上面已经演示了列表的非类型化性质