
[[0] * 3] * 2와 [[0 for _ in range(3)] for _ in range(2)]의 차이점과 깊은 복사 & 얕은 복사에 대해 알아보고자 한다.
list_1 = [[0] * 3] * 2
list_2 = [[0 for _ in range(3)] for _ in range(2)]
print(list_1)
print(list_2)
# [[0, 0, 0], [0, 0, 0]]
# [[0, 0, 0], [0, 0, 0]]
위와 같이 두 가지 방식으로 배열을 만든 다음 출력했을 때 그 결과는 동일하다.
list_1[0][1] = 1
list_2[0][1] = 1
print(list_1)
print(list_2)
Q. 배열의 [0][1] 인덱스 값을 1로 할당한 후 출력하면
둘 다 [[0, 1, 0], [0, 0, 0]] 이겠지?
A. 아쉽게도(?) 그렇지 않다.
첫 번째 배열 list_1은 출력하면 [[0, 1, 0], [0, 1, 0]] 이 나온다.

이는 list_1을 만드는 과정에서 [0, 0, 0] 리스트를 복제하고, 얕은 복사(shallow copy)가 일어났기 때문이다.
얕은 복사는 새로운 복합 객체를 만들고, (가능한 범위까지) 원본 객체를 가리키는 참조를 새로운 복합 객체에 삽입한다.
얕은 복사는 copy 모듈의 copy() 함수로도 일어나는데, 중첩된 리스트의 값을 변경하면 원본 리스트에도 그 값이 변경된다.
즉, aliasing이 발생한다.
import copy
# 원본 리스트
arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
shallow_copied_arr = copy.copy(arr) # 얕은 복사
# 얕은 복사에서는 중첩된 객체가 동일한 참조를 가진다.
shallow_copied_arr[0][0] = 100
print(arr) # [[100, 2, 3], [4, 5, 6], [7, 8, 9]]
두 번째 배열 list_2은 예상대로 [[0, 1, 0], [0, 0, 0]]이 출력된다.

[[0, 0, 0], [0, 0, 0]] 내의 리스트들은 서로 독립적이고 첫 번째 리스트 [0, 0, 0]에만 영향을 받는다.
그렇다면, 얕은 복사와 대조되는 개념인 깊은 복사(deep copy)란 무엇일까?
깊은 복사는 새로운 복합 객체를 만들고,재귀적으로 원본 객체의 사본을 새로 만든 복합 객체에 삽입한다.
copy.deepcopy()를 사용하며, 상위 객체와 중첩된 모든 객체가 새로 생성되어 원본과 완전히 독립적이다.
import copy
arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
deep_copied_arr = copy.deepcopy(arr) # 깊은 복사
deep_copied_arr[0][0] = 100
print(arr) # 원본에는 영향이 없음: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
위 코드에서 arr와 deep_copied_arr는 독립적인 객체가 되므로 aliasing이 발생하지 않으며, 한쪽의 변경이 다른 쪽에 영향을 주지 않는 것이다.
class CatBreeds:
def __init__(self, cats=None):
if cats is None:
self.cats = []
else:
self.cats = list(cats)
def drop(self, breed):
self.cats.remove(breed)
import copy
cats1 = CatBreeds(["Siamese", "Korean Shorthair", "British Shorthair", "Munchkin"])
cats2 = copy.copy(cats1)
cats3 = copy.deepcopy(cats1)
cats1.drop("Munchkin")
print(cats2.cats) # ['Siamese', 'Korean Shorthair', 'British Shorthair']
print(cats3.cats) # ['Siamese', 'Korean Shorthair', 'British Shorthair', 'Munchkin']
CatBreeds라는 클래스를 만들고, 고양이 품종이 적힌 인스턴스 cats1을 생성하였다.
이를 얕은 복사한 것이 cats2, 깊은 복사한 것이 cats3인데
cats1에서 Munchkin이라는 값을 제거했더니 얕은 복사한 cats2 객체의 cats 리스트에도 Munchkin 값이 사라졌고, cats3의 cats 리스트에는 그대로 있는 것을 확인할 수 있다.
참고