다음과 같이 리스트 내에서 반복문을 이용해 리스트를 생성하는 것을 list comprehension이라고 한다.
l = [i for i in range(10)]
# l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
위 코드는 가장 단순한 list comprehension의 예시이며, 다음과 같이 조건문을 넣을 수도 있다.
l2 = [i for i in l if i % 2]
# l2 = [1, 3, 5, 7, 9]
List comprehension을 이용하면 초기값이 있는 리스트를 간결하게 생성할 수 있으며, 리스트 외부에서 반복문을 사용하는 것보다 빠르기 때문에 실제 프로젝트에서도 자주 사용되는 문법이다.
단순히 쓰기 편리한 것 외에도, list comprehension은 리스트를 의도한 대로 사용하는 데 도움이 된다. 예를 들어 dymanic programming으로 문제를 푼다고 가정해보자. 만약 10개의 리스트가 필요하다면, 이를 한 리스트 안에 생성할 수 있는 one-liner로 다음과 같은 코드를 떠올릴 수 있다 (후술하겠지만 잘못된 코드이다).
lists = [[]] * 10
# lists = [[], [], [], [], [], [], [], [], [], []]
위 코드를 실행한 다음 lists
를 호출해보면 한 리스트 안에 10개의 리스트가 존재하기 때문에 마치 의도한 대로 잘 작동한 것처럼 보인다. 그런데 리스트 중 하나에 원소를 추가하고 다시 호출해보면 이상한 결과가 나온다.
lists[0].append(1)
# lists = [[1], [1], [1], [1], [1], [1], [1], [1], [1], [1]]
우리는 분명히 0번째 리스트에만 원소를 추가했는데 모든 리스트에 원소가 추가된 것처럼 보인다. 왜 이렇게 되는 걸까? 파이썬에서 리스트를 생성할 때, 우리가 받는 것은 리스트 자체가 아니라 리스트의 주소값이다. 우리는 이 주소값을 통해 리스트에 접근할 수 있다. 즉 l = []
와 같이 리스트를 선언하면 l
이 저장하는 값은 리스트 자체가 아닌 리스트의 주소값인 것이다. 결과적으로 lists = [[]] * 10
과 같이 리스트들을 생성하면, lists
는 동일한 주소값을 열 개 갖고 있는 리스트가 될 뿐이다. 우리는 lists
가 서로 다른 리스트 10개를 담고 있기를 원하기 때문에 이는 의도한 대로 리스트가 생성되지 않았음을 의미한다.
List comprehension을 이용하면 우리가 의도한 대로 리스트를 만들 수 있다.
lists = [[] for _ in range(10)]
위 코드에서는 반복문을 이용해서 리스트들을 생성했기 때문에 lists
안에는 서로 다른 10개의 리스트의 주소값이 담기게 된다. 마찬가지 원리로 10 x 10의 2차원 리스트 안에 서로 다른 리스트들을 만들고 싶으면 다음과 같이 하면 된다.
lists = [[[] for _ in range(10)] for _ in range(10)]