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

Python 子列表中意外反映的列表更改列表,python,list,nested-lists,mutable,Python,List,Nested Lists,Mutable,我需要用Python创建一个列表列表,因此我键入了以下内容: my_list = [[1] * 4] * 3 列表如下所示: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] [[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]] 然后我更改了其中一个最里面的值: my_list[0][0] = 5 现在,我的列表如下所示: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

我需要用Python创建一个列表列表,因此我键入了以下内容:

my_list = [[1] * 4] * 3
列表如下所示:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  
然后我更改了其中一个最里面的值:

my_list[0][0] = 5
现在,我的列表如下所示:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  
这不是我想要或期望的。有人能解释一下发生了什么事,以及如何避开它吗

[[1] * 4] * 3
甚至:

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

与本例相同:

>>> 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]]
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
这可能就没那么令人惊讶了

甚至:

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

与本例相同:

>>> 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]]
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

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

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

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]
要修复它,您需要确保在每个位置创建一个新列表。一种方法是

[[1]*4 for _ in range(3)]
它将每次重新评估
[1]*4
,而不是评估一次并引用一个列表中的3个


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

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

相反,列表理解在每次迭代时都会重新评估元素表达式。
[[1]*4代表范围(3)中的n]
每次都会重新评估
[1]*4
,原因相同
[x**2代表范围(3)]
每次都会重新评估
x**2
*4生成一个新列表,因此列表理解功能可以满足您的需要


顺便说一句,
[1]*4
也不会复制
[1]
的元素,但这并不重要,因为整数是不可变的。你不能像
1.value=2
那样把1变成2。

当你写
[x]*3
时,你基本上得到的是列表
[x,x,x]
。也就是说,一个列表包含对同一个
x
的三个引用。然后修改此
x
时,通过对它的所有三个引用可见:

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

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]
要修复它,您需要确保在每个位置创建一个新列表

[[1]*4 for _ in range(3)]
它将每次重新评估
[1]*4
,而不是评估一次并引用一个列表中的3个


您可能想知道为什么
*
不能像列表理解那样生成独立的对象。这是因为乘法运算符
*
对对象进行运算,而看不到表达式。当您使用
*
[[1]*4]
乘以3时,
*
只看到1元素列表
[[1]*4]
的计算结果是,而不是
[[1]*4
表达式文本。
*
不知道如何复制该元素,不知道如何重新计算
[[1]*4]
,甚至不知道您想要复制,而且一般来说,甚至可能没有复制该元素的方法

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

相反,列表理解在每次迭代时都会重新评估元素表达式。
[[1]*4代表范围(3)中的n]
每次都会重新评估
[1]*4
,原因相同
[x**2代表范围(3)]
每次都会重新评估
x**2
*4生成一个新列表,因此列表理解功能可以满足您的需要


顺便说一句,
[1]*4
也不会复制
[1]
的元素,但这并不重要,因为整数是不可变的。你不能像
1.value=2
那样把1变成2。

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

你写

lst = [[1] * 4] * 3
这相当于:

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

lst[0][0] = 5
lst1[0] = 5
As
lst[0]
不过是
lst1

要获得所需的行为,可以使用列表:

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

在本例中,表达式将针对每个
n
重新求值,从而生成一个不同的列表。

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

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

lst = [[1] * 4] * 3
这相当于:

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

lst[0][0] = 5
lst1[0] = 5
As
lst[0]
不过是
lst1

要获得所需的行为,可以使用列表:

lst = [ [1]*4 for n in range(3) ]
在这种情况下,将针对每个
n
重新计算表达式,从而生成不同的列表
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]]
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]]
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]]
my_list = [[1]*4 for _ in range(3)]
my_list = [[1 for __ in range(4)] for _ in range(3)]
>>> 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
    arr = [[0]*cols]*row
   rows, cols = (5, 5) 
   arr = [[0 for i in range(cols)] for j in range(rows)] 

   arr = [0]*N 
   arr = [0 for i in range(N)] 
>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
>>> A
[[None, None], [None, None], [None, None]]
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
node_count = 4
colors = [0,1,2,3]
sol_dict = {node:colors for node in range(0,node_count)}
>>> sol_dict
{0: [0, 1, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
>>> [v is colors for v in sol_dict.values()]
[True, True, True, True]
>>> sol_dict[0].remove(1)
>>> sol_dict
{0: [0, 2, 3], 1: [0, 2, 3], 2: [0, 2, 3], 3: [0, 2, 3]}
>>> colors = [0,1,2,3]
>>> sol_dict = {node:colors[:] for node in range(0,node_count)}
>>> sol_dict
{0: [0, 1, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
>>> sol_dict[0].remove(1)
>>> sol_dict
{0: [0, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}