首页 文章

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

提问于
浏览
453

我需要在Python中创建一个列表列表,所以我输入以下内容:

myList = [[1] * 4] * 3

列表看起来像这样:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

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

myList[0][0] = 5

现在我的列表看起来像这样:

[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

这不是我想要的或期望的 . 有人可以解释一下发生了什么,以及如何解决这个问题?

12 回答

  • 1

    通过使用内置列表功能,您可以这样做

    a
    out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
    #Displaying the list
    
    a.remove(a[0])
    out:[[1, 1, 1, 1], [1, 1, 1, 1]]
    # Removed the first element of the list in which you want altered number
    
    a.append([5,1,1,1])
    out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]]
    # append the element in the list but the appended element as you can see is appended in last but you want that in starting
    
    a.reverse()
    out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
    #So at last reverse the whole list to get the desired list
    
  • 1

    让我们以下列方式重写您的代码:

    x = 1
    y = [x]
    z = y * 4
    
    myList = [z] * 3
    

    然后,运行以下代码,使一切更清晰 . 代码的作用基本上是打印获取对象的id,其中

    返回对象的“身份”

    并将帮助我们识别它们并分析发生的情况:

    print("myList:")
    for i, subList in enumerate(myList):
        print("\t[{}]: {}".format(i, id(subList)))
        for j, elem in enumerate(subList):
            print("\t\t[{}]: {}".format(j, id(elem)))
    

    您将获得以下输出:

    x: 1
    y: [1]
    z: [1, 1, 1, 1]
    myList:
        [0]: 4300763792
            [0]: 4298171528
            [1]: 4298171528
            [2]: 4298171528
            [3]: 4298171528
        [1]: 4300763792
            [0]: 4298171528
            [1]: 4298171528
            [2]: 4298171528
            [3]: 4298171528
        [2]: 4300763792
            [0]: 4298171528
            [1]: 4298171528
            [2]: 4298171528
            [3]: 4298171528
    

    那么现在让我们一步一步走 . 您有 x ,即 1 ,以及包含 x 的单个元素列表 y . 你的第一步是 y * 4 ,它会给你一个新的列表 z ,它基本上是 [x, x, x, x] ,即它创建一个新的列表,它将有4个元素,它们是对初始 x 对象的引用 . 净步骤非常相似 . 您基本上执行 z * 3 ,即 [[x, x, x, x]] * 3 并返回 [[x, x, x, x], [x, x, x, x], [x, x, x, x]] ,原因与第一步相同 .

  • 387

    Python容器包含对其他对象的引用 . 看这个例子:

    >>> a = []
    >>> b = [a]
    >>> b
    [[]]
    >>> a.append(1)
    >>> b
    [[1]]
    

    在此 b 是一个列表,其中包含一个项目,该项目是列表 a 的引用 . 列表 a 是可变的 .

    列表乘以整数等效于将列表多次添加到自身(请参阅common sequence operations) . 继续这个例子:

    >>> c = b + b
    >>> c
    [[1], [1]]
    >>>
    >>> a[0] = 2
    >>> c
    [[2], [2]]
    

    我们可以看到列表 c 现在包含对列表 a 的两个引用,它相当于 c = b * 2 .

    Python FAQ还包含对此行为的说明:How do I create a multidimensional list?

  • 5

    简单来说,这种情况正在发生,因为在python中一切正常 by reference ,所以当你创建一个列表列表时,你基本上就会遇到这样的问题 .

    要解决您的问题,您可以执行以下任一操作:1 . 使用numpy数组documentation for numpy.empty 2.在到达列表时附加列表 . 如果你愿意,你也可以使用字典

  • 1

    试图更具描述性地解释它,

    操作1:

    x = [[0, 0], [0, 0]]
    print(type(x)) # <class 'list'>
    print(x) # [[0, 0], [0, 0]]
    
    x[0][0] = 1
    print(x) # [[1, 0], [0, 0]]
    

    操作2:

    y = [[0] * 2] * 2
    print(type(y)) # <class 'list'>
    print(y) # [[0, 0], [0, 0]]
    
    y[0][0] = 1
    print(y) # [[1, 0], [1, 0]]
    

    注意到为什么没有't modifying the first element of the first list didn' t修改每个列表的第二个元素?那是因为 [0] * 2 确实是两个数字的列表,并且无法修改对0的引用 .

    如果要创建克隆副本,请尝试操作3:

    import copy
    y = [0] * 2   
    print(y)   # [0, 0]
    
    y = [y, copy.deepcopy(y)]  
    print(y) # [[0, 0], [0, 0]]
    
    y[0][0] = 1
    print(y) # [[1, 0], [0, 0]]
    

    另一种创建克隆副本的有趣方法,操作4:

    import copy
    y = [0] * 2
    print(y) # [0, 0]
    
    y = [copy.deepcopy(y) for num in range(1,5)]
    print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]
    
    y[0][0] = 5
    print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]
    
  • 1
    [[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]]
    

    它可能有点不那么令人惊讶 .

  • 28

    当你写 [x]*3 时,你基本上得到了列表 [x, x, x] . 也就是说,一个列表中有3个引用相同的 x . 然后,当您修改此单个 x 时,通过对它的所有三个引用都可以看到它 .

    要解决此问题,您需要确保在每个位置创建新列表 . 一种方法是

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

    这将每次重新评估 [1]*4 而不是评估它一次,并对3个列表进行3次引用 .


    您可能想知道为什么 * 可以't make independent objects the way the list comprehension does. That'因为乘法运算符 * 对对象进行操作而不会看到表达式 . 当您使用 *[[1] * 4] 乘以3时, * 仅查看_元素列表 [[1] * 4] 的计算结果,而不是 [[1] * 4 表达式文本 . * 不知道如何制作该元素的副本,不知道如何重新评估 [[1] * 4] ,也不知道你甚至想要副本,一般来说,甚至可能没有办法复制该元素 .

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

    相反,列表推导会重新评估每次迭代时的元素表达式 . 每次出于同样的原因 [[1] * 4 for n in range(3)] 重新评估 [1] * 4 每次都会重新评估 x**2 . 对 [1] * 4 的每次评估都会生成一个新列表,因此列表理解可以满足您的需求 .

    顺便提一下, [1] * 4 也不会复制 [1] 的元素,但是 1.value = 2 不会像 1.value = 2 那样做,并将1变为2 .

  • 1

    我猜大家都在解释发生了什么 . 我建议一种解决方法:

    myList = [[1 for i in range(4)] for j in range(3)]

    myList[0][0] = 5
    

    print myList

    然后你有:

    [[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
    
  • 4
    size = 3
    matrix_surprise = [[0] * size] * size
    matrix = [[0]*size for i in range(size)]
    

    Frames and Objects

    Live Python Tutor Visualize

  • 40

    除了正确解释问题的接受答案之外,在列表理解中,如果您使用的是python-2.x,请使用 xrange() 返回更高效的生成器(在python 3中 range() 执行相同的工作) _ 而不是一次性变量 n

    [[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]
    

    此外,作为更多Pythonic方式,您可以使用itertools.repeat()来创建重复元素的迭代器对象:

    >>> a=list(repeat(1,4))
    [1, 1, 1, 1]
    >>> a[0]=5
    >>> a
    [5, 1, 1, 1]
    

    附:使用numpy,如果你只想创建一个1或0的数组,你可以使用 np.onesnp.zeros 和/或其他数字使用 np.repeat()

    In [1]: import numpy as np
    
    In [2]: 
    
    In [2]: np.ones(4)
    Out[2]: array([ 1.,  1.,  1.,  1.])
    
    In [3]: np.ones((4, 2))
    Out[3]: 
    array([[ 1.,  1.],
           [ 1.,  1.],
           [ 1.,  1.],
           [ 1.,  1.]])
    
    In [4]: np.zeros((4, 2))
    Out[4]: 
    array([[ 0.,  0.],
           [ 0.,  0.],
           [ 0.,  0.],
           [ 0.,  0.]])
    
    In [5]: np.repeat([7], 10)
    Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])
    
  • 2

    实际上,这正是您所期望的 . 让我们分解这里发生的事情:

    你写

    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 xrange(3) ]
    

    在这种情况下,对每个n重新计算表达式,从而得到不同的列表 .

  • 98

    myList = [[1]*4] * 3 在内存中创建一个列表对象 [1,1,1,1] 并将其引用复制3次 . 这相当于 obj = [1,1,1,1]; myList = [obj]*3 . 对 obj 的任何修改都将反映在三个地方,无论列表中是否引用了 obj . 正确的陈述是:

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

    要么

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

    Important thing to note here* 运算符主要用于创建 list of literals . 由于 1 是一个文字,因此 obj =[1]*4 将创建 [1,1,1,1] ,其中每个 1 是原子的 not 1 的引用重复4次 . 这意味着如果我们做 obj[2]=42 ,那么 obj 将变成 [1,1,42,1] not [42,42,42,42],正如有些人可能认为的那样 .

相关问题