개발자가 되고 싶은 준개발자

[파이썬] 리스트의 리스트(list of list) 생성하기 본문

트러블 슈팅/파이썬

[파이썬] 리스트의 리스트(list of list) 생성하기

준개발자 2020. 9. 16. 18:26

문제

list_of_list = [[0]*5]*5

위처럼 리스트의 리스트를 생성하면 [[0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0]]가 만들어진다.

여기까지는 좋다. 그런데 리스트의 일부분을 수정하면 어떻게 될까?

for i in range(5):
    list_of_list[0][i] = 1

위의 코드를 이어 실행하면, list_of_list가 [[1,1,1,1,1], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0]]이 되어야 할 것 같다.

그런데 실제로는 [[1,1,1,1,1], [1,1,1,1,1], [1,1,1,1,1], [1,1,1,1,1], [1,1,1,1,1]]이 된다. (띠용? 당황스럽다...)


해결 방법

문제는 처음에 리스트의 리스트를 만드는 과정에 있다. 객체의 id(파이썬에서 객체를 구별하는 고유값)를 프린트해보자.

for i in range(5):
    for j in range(5):
        print(id(list_of_list[i][j]))

이럴수가... 모두 같은 번호가 프린트된다.. 모두 같은 객체이었던 것이다.

그러면 어떻게 해결해야 할까..

better_list_of_list = [[0 for _ in range(5)] for _ in range(5)]

위처럼 list를 작성하면 앞에서 언급한 리스트를 수정할 때 다른 리스트들도 함께 수정되는 현상이 해결된다.


왜 그런지 더 자세히 알아보자 :)

list_of_list = [[0]*5]*5와 better_list_of_list = [[0 for _ in range(5)] for _ in range(5)]가 다른 결과를 내는 이유는 파이썬 컴파일러가 작동하는 방식 때문이다.

list_of_list = [[0]*5]*5를 만들때는 [0]를 우선 만들고, 이에 대한 copy를 생성한다. (즉 c언어로 설명하면 객체는 하나이고, 나머지는 객체에 대한 포인터가 되는 셈이다.)

이에 반해 better_list_of_list = [[0 for _ in range(5)] for _ in range(5)]를 실행하면 컴파일러는 안쪽 괄호에서 5번 0를 만들고, 똑같은 과정을 바깥쪽 괄호에서 5번씩 실행한다.

이렇게 list를 생성하는 방식을 파이썬에서는 list comprehension이라 부른다. 

List = [expression for variable in sequence]
List comprehension은 sequence에 있는 모든 아이템에 대한 expression을 한번씩 평가한다. 파이썬은 expression을 실행할 때 새로운 객체를 만든다.

참조

www.effbot.org/zone/python-list.htm#modifying

stackoverflow.com/questions/12791501/python-initializing-a-list-of-lists