하위 목록에 예기치 않게 반영된 목록 변경 목록


645

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

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

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

답변:


560

글을 쓰면 [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로 바꿀 수 있습니다 .


24
나는 아무도 그것을 지적하지 않는다는 것에 놀랐습니다. 변경 가능한 경우에만 [x]*33 개의 참조를 저장하십시오 . 예를 들어이 does't 작업 , 어디 한 후 ,[x, x, x]xa=[4]*3a[0]=5a=[5,4,4].
Allanqunzi

42
기술적으로는 여전히 정확합니다. [4]*3본질적으로와 같습니다 x = 4; [x, x, x]. 그러나 이것이 불변 이기 때문에 아무런 문제4 가 발생하지 않는다는 것은 사실입니다 . 또한 다른 예는 실제로 다른 경우가 아닙니다. a = [x]*3; a[0] = 5경우에도 문제가 발생하지 않습니다 x당신은 수정하지 않는 때문에, 변경 가능한 x만 수정 a. 나는 내 대답을 오도하거나 잘못된 것으로 묘사하지 않을 것입니다- 불변의 물건을 다루는 경우 발로 자신을 쏠 수 없습니다 .
CAdaker

19
@ Allanqunzi 당신이 잘못되었습니다. 할 x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. 파이썬은 여기서 변경 가능한 객체와 변경 불가능한 객체를 구별하지 않습니다.
timgeb

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

프레임과 객체

라이브 파이썬 튜터 시각화


그래서 왜 우리가 matrix = [[x] * 2]를 쓰면 설명하는 예제와 같은 동일한 객체에 대해 2 개의 요소를 만들지 않아도 같은 개념 인 것 같습니다.
Ahmed Mohamed

@AhmedMohamed 실제로 그것은 똑같은 객체의 두 가지 요소로 구성된 목록을 만듭니다 x. 당신이 세계적으로 독특한 물건을 x = object()만들고 다음과 matrix = [[x] * 2]같이 실현 matrix[0][0] is matrix[0][1]
한다면

@nadrimajstor이므로 왜 matrix [0]의 변경이 2d matrix를 사용한 위의 예와 같이 matrix [1]에 영향을 미치지 않는가?
Ahmed Mohamed

당신이 (우리의 예에서 그것은이다 가변 시퀀스의 "복사"를 할 때 @AhmedMohamed 서프라이즈 올 list) 그래서 만약 row = [x] * 2(A)보다 matrix = [row] * 2두 행이 정확히 같은 객체, 지금은 하나의 행으로 변경 곳이 matrix[0][0] = y갑자기 다른 하나에 반영(matrix[0][0] is matrix[1][0]) == True
nadrimajstor을

@AhmedMohamed 더 나은 설명을 제공 할 수있는 Python 이름과 값에 대한 Ned Batchelder-Facts and Myths를 살펴보십시오 . :)
nadrimajstor

52

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

당신은 쓰기

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에 대해 식을 다시 평가하여 다른 목록을 만듭니다.


여기에 좋은 대답 그냥 작은 또한 : 당신은 당신이 동일한 개체에서 나왔습니다 거래하는 경우 것이 분명입니다 id(lst[0][0])id(lst[1][0])심지어 나 id(lst[0])하고id(lst[1])
세르지 Kolodyazhnyy

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

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


3
"is"연산자를 사용하여이를 발견 할 수 있습니다. ls [0] is ls [1]은 True를 반환합니다.
mipadi

9

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

6

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

>>> 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에는이 동작에 대한 설명도 포함되어 있습니다. 다차원 목록을 작성하는 방법


6

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

2
리터럴에 관한 것이 아닙니다. 인덱스 obj[2] = 42 가 참조2 하는 객체를 변경하는 것과는 반대로 index 가 참조대체합니다myList[2][0] = ... ( myList[2]목록이며 어설 션은 인덱스가 0 인 참조를 목록에서 변경 함). 물론 정수는 변경할 수 없지만 많은 객체 유형 있습니다. 그리고 [....]리스트 표시 표기법도 리터럴 구문의 한 형태입니다! 변경 가능 대 변경 불가능한 오브젝트와 복합 (예 : 목록) 및 스칼라 오브젝트 (예 : 정수)를 혼동하지 마십시오.
Martijn Pieters

5

간단히 말해서 이것은 파이썬에서 모든 것이 참조로 작동하기 때문에 발생합니다 . 따라서 목록 목록을 만들면 기본적으로 그러한 문제가 발생합니다.

문제를 해결하려면 다음 중 하나를 수행하십시오. 1. numpy.empty에 대해 numpy 배열 설명서를 사용하십시오 . 2. 목록에 도달하면 목록을 추가하십시오. 3. 원하는 경우 사전을 사용할 수도 있습니다


2

다음과 같은 방법으로 코드를 다시 작성하십시오.

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

myList = [z] * 3

그런 다음이 코드를 실행하여 모든 것을보다 명확하게하십시오. 코드가하는 일은 기본적으로 id얻은 객체 의 s를 인쇄하는 것 입니다.

객체의 "정체성"을 반환

이를 식별하고 어떤 일이 발생하는지 분석하는 데 도움이됩니다.

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

이제 단계별로 진행하겠습니다. 당신이 x1, 그리고 하나의 요소 목록이 y포함 x. 첫 번째 단계는 기본적으로 y * 4라는 새 목록을 얻는 것입니다. 즉, 초기 객체에 대한 참조 인 4 개의 요소가있는 새 목록을 만듭니다 . 순 단계는 매우 비슷합니다. 당신은 기본적으로 수행 되는, 반품 및 첫 번째 단계와 동일한 이유.z[x, x, x, x]xz * 3[[x, x, x, x]] * 3[[x, x, x, x], [x, x, x, x], [x, x, x, x]]


2

모든 사람들이 무슨 일이 일어나고 있는지 설명합니다. 나는 그것을 해결하는 한 가지 방법을 제안한다.

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

2

보다 설명 적으로 설명하려고하면

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

첫 번째 목록의 첫 번째 요소를 수정하지 않아도 각 목록의 두 번째 요소가 수정되지 않은 이유는 무엇입니까? [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]]

2

파이썬리스트 곱셈 에서 @spelchekr : [[...]] * 3는 수정 될 때 서로를 미러링하는 3 개의리스트를 만들고, "왜 외부 * 3만이 더 많은 참조를 생성하지만 내부는 그렇지 않습니까? "왜 모두 1이 아닌가?"

li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]

ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

위의 코드를 시도한 후의 설명은 다음과 같습니다.

  • 내부는 *3또한 참조를 만들지 만의 참조는 같은 불변 [&0, &0, &0]변화 할 때 다음 li[0], 당신은 CONST INT의 기본 참조를 변경할 수 없습니다 0그냥 새에 참조 주소를 변경할 수 있도록 &1;
  • 동안 ma=[&li, &li, &li]li, 변경 가능한 전화 할 때 너무 ma[0][0]=1, 엄마 [0] [0] 동일하게하는 것입니다 &li[0]모든 때문에, &li인스턴스에 자사의 첫번째 주소가 변경됩니다 &1.

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
두 번째 단계를 수행하면 네 번째 단계를 삭제할 수 있습니다.a.insert(0,[5,1,1,1])
U10-Forward
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.