8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

列表的更改意外反映在子列表中

Brandon Baker 2月前

191 0

我创建了一个列表列表:>>>​​ xs = [[1] * 4] * 3>>> print(xs)[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]然后,我更改了最里面的一个值:>>> xs[0][0] = 5...

我创建了一个列表:

>>> 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


参见:

  • p5

    p6

  • p7

  • p8

帖子版权声明 1、本帖标题:列表的更改意外反映在子列表中
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Brandon Baker在本站《list》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 当以其他方式创建列表列表(但存在相同问题)时,是否有更具体的问题?例如,通过使用

  • 要在 Python 中使用矩阵,只需转到 numpy。列表的列表确实很麻烦而且很慢。

  • 当你写入时 [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 次引用。


    您可能想知道为什么 * 不能像列表推导那样创建独立对象。这是因为乘法运算符 * 对对象进行操作,而不查看表达式。当您使用 * 乘以 [[1] * 4] 3 时, * 只会看到 1 个元素列表的 [[1] * 4] 计算结果,而不是 [[1] * 4 表达式文本。 * 不知道如何复制该元素,不知道如何重新评估 [[1] * 4] ,甚至不知道您是否想要副本,并且通常甚至可能没有复制元素的方法。

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

    相比之下,列表推导式在每次迭代时都会重新评估元素表达式。 [[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 的操作。

  • 我很惊讶没有人指出,这里的答案具有误导性。 [x]*3 存储 3 个引用,如 [x, x, x],只有当 x 可变时才正确。这对于例如 a=[4]*3 不起作用,其中 a[0]=5 之后,a=[5,4,4]。

  • 从技术上讲,它仍然是正确的。[4]*3 本质上等同于 x = 4; [x, x, x]。不过,这确实不会引起任何问题,因为 4 是不可变的。另外,您的另一个示例并不是一个不同的情况。a = [x]*3; a[0] = 5 即使 x 是可变的也不会引起问题,因为您没有修改 x,只修改了 a。我不会将我的答案描述为误导或不正确 - 如果您处理的是不可变对象,您就不能搬起石头砸自己的脚。

  • @Allanqunzi 你错了。Do x = 1000; lst = [x]*2; lst[0] is lst[1] -> True。Python 在这里根本不区分可变对象和不可变对象。

  • 有人可以在 docs.python.org 上找到有关 * 运算符的文档吗?我试过了,但没有找到。

  • D K 2月前 0 只看Ta
    引用 9
    size = 3
    matrix_surprise = [[0] * size] * size
    matrix = [[0]*size for _ in range(size)]
    

    使用Python Tutor进行 实时可视化

    Frames and Objects

  • Vski 2月前 0 只看Ta
    引用 10

    那么,为什么如果我们写 matrix=[[x] * 2] 不会像您描述的例子那样为同一个对象生成 2 个元素,它似乎是相同的概念,我遗漏了什么?

  • @AhmedMohamed 确实,它确实会创建一个列表,其中包含 x 引用的完全相同的对象的两个元素。如果您使用 x = object() 创建全局唯一对象,然后使 matrix = [[x] * 2] ,则这些确实成立:matrix[0][0] 是 matrix[0][1]

  • @nadrimajstor 那么为什么矩阵[0]的变化不会影响矩阵[1],就像上面的二维矩阵的例子一样。

  • @AhmedMohamed 当你制作可变序列的 \'副本\' 时(在我们的例子中它是一个列表),惊喜就会出现,所以如果一行 = [x] * 2,那么矩阵 = [row] * 2,其中两行完全相同的对象,现在更改为一行 matrix[0][0] = y 突然反映在另一行中(matrix[0][0] 是 matrix[1][0])== True

  • @AhmedMohamed 看一下 Ned Batchelder - 有关 Python 名称和值的事实和神话,因为它可能会提供更好的解释。:)

  • 事实上,这正是你所期望的。让我们分解一下这里发生的事情:

    你写

    lst = [[1] * 4] * 3
    

    这相当于:

    lst1 = [1]*4
    lst = [lst1]*3
    

    这意味着 lst 是一个包含 3 个元素的列表,所有元素都指向 lst1 。这意味着以下两行是等效的:

    lst[0][0] = 5
    lst1[0] = 5
    

    只不过 lst[0] lst1 .

    为了获得所需的行为,您可以使用列表推导:

    lst = [ [1]*4 for n in range(3) ]
    

    在这种情况下,将对每个表达式重新评估 n ,从而得到不同的列表。

  • 引用 16

    这里只是对这个好答案的一个小补充:如果你执行 id(lst[0][0]) 和 id(lst[1][0]) 甚至 id(lst[0]) 和 id(lst[1]),很明显你处理的是同一个对象

  • @JobHunter69 复制也不行;这全都是假象。对于数字或字符串等不可变对象的一维列表,您无法更改这些列表元素;您只能用新值替换它们。看起来您有一个副本,但实际上您没有。二维列表实际上是列表的列表,您可以轻松更改这些内部列表。

  • [[1] * 4] * 3
    

    甚至:

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

    创建一个引用内部 [1,1,1,1] 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]]
    

    这也许就不那么令人惊讶了。

  • 您可以使用 \'is\' 运算符来发现这一点。ls[0] is ls[1] 返回 True。

  • my_list = [[1]*4] * 3 创建一个列表对象 [1,1,1,1] 并将其引用复制 3 次。这相当于 obj = [1,1,1,1]; my_list = [obj]*3 。对 的任何修改 obj 都将反映在 obj 列表中引用 的三个地方。正确的语句应该是:

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

    或者

    my_list = [[1 for __ in range(4)] for _ in range(3)]
    

    这里要注意的重要一点 是, * 运算符 主要 用于创建 文字列表 。虽然 1 是不可变的,但 obj = [1]*4 仍然会创建一个 1 重复 4 次的列表以形成 [1,1,1,1] 。但是,如果对不可变对象进行了任何引用,则该对象将被新对象覆盖。

    这意味着如果我们这样做 obj[1] = 42 ,那么 obj 不再 [1,42,1,1] 变成 [42,42,42,42]。这也可以得到验证:

    >>> my_list = [1]*4
    >>> my_list
    [1, 1, 1, 1]
    
    >>> id(my_list[0])
    4522139440
    >>> id(my_list[1])  # Same as my_list[0]
    4522139440
    

    >>> my_list[1] = 42  # Since my_list[1] is immutable, this operation overwrites my_list[1] with a new object changing its id.
    >>> my_list
    [1, 42, 1, 1]
    
    >>> id(my_list[0])
    4522139440
    >>> id(my_list[1])  # id changed
    4522140752
    >>> id(my_list[2])  # id still same as my_list[0], still referring to value `1`.
    4522139440
    
返回
作者最近主题: