[1] * 5
# [1, 1, 1, 1, 1]
파이썬에는 리스트 반복 (list repetition)이라는 기능이 있습니다. 위 코드처럼 리스트에 정수를 곱해 리스트 내의 원소를 반복시킬 수 있는 기능입니다.
[1, 2] * 2
# [1, 2, 1, 2]
리스트 내의 원소가 두 개 이상일 때에도 마찬가지입니다. 모든 원소가 반복된 리스트를 반환합니다.
위 두 예시에서는 리스트의 원소가 숫자인 경우만 보였지만, 어떤 타입의 원소이건 모두 잘 동작합니다. 원소가 리스트(list), 집합(set), 딕셔너리(dict)인 경우에도 동작한다는 겁니다. 이 세 타입의 공통점이 무엇일까요? 바로 mutable, 즉 수정 가능한 타입이라는 겁니다.
이 타입들에 대해 리스트 반복을 사용할 때 주의할 점이 있습니다. 리스트 반복은 얕은 복사(shallow copy)를 수행한다는 것입니다. 따라서 리스트 반복으로 반복된 값 중 하나라도 수정되면 모든 값이 같이 수정됩니다. 예시를 살펴봅시다.
a = [[0]] * 2
# [[0], [0]]
a[0].append(1)
# 우리의 예상: [[0, 1], [0]]
# 실제 결과: [[0, 1], [0, 1]]
리스트 반복으로 배열을 두 번 반복시켰습니다. 그 다음 a[0]에 대해 append() 메소드로 원소를 추가했습니다. a[0]에만 원소가 추가되리라 생각할 수 있습니다. 하지만 실제로는 a[0]과 a[1] 모두에 원소가 추가됩니다.
다시 한 번 말하지만, 리스트 반복은 얕은 복사를 수행합니다. 따라서 반복된 모든 객체는 같은 객체입니다. 값을 공유한다는 겁니다. 같은 원리의 예시를 하나 더 살펴보도록 합시다.
a = [0]
b = a
b.append(1)
print(a)
# 우리의 예상: [0]
# 실제 결과: [0, 1]
a라는 배열을 만들고, b에 a를 대입했습니다. 그 다음 b에 대해서 append() 메소드로 원소를 추가했습니다. b에만 원소가 추가되리라 생각할 수 있습니다. 하지만 마찬가지로 a와 b 모두에 원소가 추가됩니다.
위 두 예시에서 우리의 예상과 실제 결과가 다른 것은 정확히 똑같은 이유로부터 기인합니다. 얕은 복사입니다. a[1]은 a[0]을 얕은 복사한 것이고, b는 a를 얕은 복사한 것입니다. 얕은 복사를 수행하였기 때문에 모두 같은 객체를 가리킵니다. 따라서 값을 공유하는 겁니다.
개인적으로 알고리즘 문제를 풀 때 리스트 반복 기능을 자주 이용하는 편입니다. 그런데 리스트 반복이 얕은 복사를 수행한다는 걸 몰라서 헤맨 경험이 있어 포스트해보았습니다. 여러분은 그러지 않길 바랍니다... ㅎ 🥲