하위 목록에 예기치 않게 반영된 목록 변경 목록 [[1, 1, 1, 1],

파이썬으로 목록 목록을 만들어야했기 때문에 다음과 같이 입력했습니다.

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

내가 원하거나 기대 한 것이 아닙니다. 누군가 무슨 일이 일어나고 있는지, 어떻게 해결할 수 있습니까?



답변

글을 쓰면 [x]*3본질적으로 목록을 얻습니다 [x, x, x]. 즉, 같은에 대한 3 개의 참조가있는 목록입니다 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한 번 평가하지 않고 매번 1 개의 목록을 참조하는 대신 매번 재평가 합니다.


*목록 이해와 같은 방식으로 독립 객체를 만들 수 없는지 궁금 할 것입니다. 곱셈 연산자 *는 표현식을 보지 않고 객체에서 작동 하기 때문 입니다. 3 *을 곱하는 데 사용 하면 식 텍스트가 아닌 1 요소 목록 만 평가됩니다 . 해당 요소의 사본을 만드는 방법 , 재평가 방법, 모방조차 원치 않으며 일반적으로 요소를 복사 할 수있는 방법이 없을 수도 있습니다.[[1] * 4]*[[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 = 21을 2로 바꿀 수 있습니다 .


답변

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

프레임과 객체

라이브 파이썬 튜터 시각화


답변

실제로 이것은 정확히 당신이 기대하는 것입니다. 여기서 일어나는 일을 분해 해 봅시다 :

당신은 쓰기

lst = [[1] * 4] * 3

이것은 다음과 같습니다.

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

이것은 lst3 개의 요소가 모두를 가리키는 목록 임을 의미 합니다 lst1. 이것은 다음 두 줄이 동일하다는 것을 의미합니다.

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

으로는 lst[0]아무것도하지만입니다 lst1.

원하는 동작을 얻으려면 목록 이해를 사용할 수 있습니다.

lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

이 경우 각 n에 대해 식을 다시 평가하여 다른 목록을 만듭니다.


답변

[[1] * 4] * 3

또는:

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

[1,1,1,1]내부 목록의 3 개 사본이 아닌 내부를 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]]

아마도 조금 덜 놀랍습니다.


답변

python-2.x를 사용 xrange()하는 경우 python-2.x를 사용 하는 경우 끔찍한 변수 대신 보다 효율적인 ( range()파이썬 3에서 동일한 작업을 수행하는) 생성기를 반환하는 목록 이해 내에서 문제를 올바르게 설명하는 허용 된 답변과 함께 :_n

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

또한 훨씬 더 파이썬적인 방법 itertools.repeat()으로 반복 요소의 반복자 객체를 만드는 데 사용할 수 있습니다 .

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

당신이 유일한 사람 또는 제로 사용할 수의 배열을 만들려면 PS는 NumPy와 사용 np.ones하고 np.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])


답변

파이썬 컨테이너는 다른 객체에 대한 참조를 포함합니다. 이 예제를보십시오 :

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

여기 b에는 list에 대한 참조 인 하나의 항목이 포함 된 목록이 있습니다 a. 리스트 a는 변경 가능합니다.

리스트에 정수를 곱하는 것은리스트 자체에 여러 번 추가하는 것과 같습니다 ( 공통 시퀀스 연산 참조 ). 따라서 예제를 계속 진행하십시오.

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

우리는 목록을 볼 수 있습니다 c이제 목록에 두 개의 참조가 포함되어 a동일합니다 c = b * 2.

Python FAQ에는이 동작에 대한 설명도 포함되어 있습니다. 다차원 목록을 작성하는 방법


답변

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

여기서주의 할 점*연산자가 리터럴 목록 을 작성하는 데 주로 사용된다는 것 입니다. 불변 이지만 , 4 번 반복 하여 목록을 생성합니다 . 그러나 변경할 수없는 객체에 대한 참조가 있으면 해당 객체를 새 객체로 덮어 씁니다.1obj =[1]*41[1,1,1,1]

이것은 우리가 그렇게한다면 obj[1]=42, 일부 사람들이 obj예상 한대로 [1,42,1,1] 되지 않을 것임을 의미합니다 [42,42,42,42]. 이것은 또한 확인할 수 있습니다 :

>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440

>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440