다른 분의 코딩을 보다 이게 왜 돌아가지했던 부분이 생겨 찾아보다 알게된 것이다.
일단 의문이 생긴 코딩을 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | actors = Actor.objects.all() results = [] for actor in actors: title_list = [] results.append( { "first_name": actor.first_name, "last_name": actor.last_name, "title": title_list, } ) titles = actor.movies.all() for title in titles: title_list.append( { "title": title.title } ) | cs |
여기서 반복문 처음에 title_list = []로 초기화를 주었는데 출력값이 정상적으로 나왔다. 당연히 빈 리스트가 반복적으로 나올꺼라 생각했지만 어떻게 results.append()안의 title_list에 아래쪽의 .append()의 값이 들어 갈 수 있을까?
이에 대한 정답은 얕은 복사와 깊은 복사에서 찾을 수 있었다.
이러한 코드가 가능한데에는 python에서 복사를 할때 그 값을 어떻게 저장하느냐를 보면 알 수 있다.
기존에 생각하던 복사는 단순 객체 복사이다.
예시로 알아보자
a_list = [1,2,3,4]
b_list = a_list
print(b) # [1,2,3,4]
b_list[1] = 5
print(b_list) # [5,2,3,4]
print(a_list) # [5,2,3,4]
분명 b_list[1]만 값을 변경했는데 a_list까지 값이 변경되었다.
그 이유는 b_list = a_list라는 코드가 복사를 시킨다는 것이 아닌 "같은 객체의 주소를 바라보게 한다." 그렇기 때문에 b_list를 수정해도 a_list도 변경된 것이다. C언어에서 포인터(*)와 같이 생각하면 된다.(주소값을 참조하여 가져온다.)
이런 부분들에 대한 객체에는 mutable과 immutable객체가 있다.
mutable ➡️ list, set, dict, user가 만든 class
immutable ➡️ bool, int, float, tuple, str, frozenset
변경가능한(mutable) 객체일때만 해당하고 불변의(immutable) 객체의 경우는 해당하지 않는다.
단순 객체 복사와 얕은 복사의 차이는 복합객체(리스트)는 별도로 생성하지만 그 안에 들어가는 요소들은 원래와 같은 객체라는 점이다.
import copy
a_list = [1, [1, 2, 3]]
b_list = copy.copy(a_list)
print(b) # [1, [1, 2, 3]]
b[0] = 20
print(b) # [20, [1, 2, 3]]
print(a) # [1, [1, 2, 3]]
c_list = copy.copy(a)
c[1].append(4)
print(c) # [1, [1, 2, 3, 4]]
print(a) # [1, [1, 2, 3, 4]]
b_list의 경우는 a_list를 복제해서 값을 넣어 준 것임으로 immutable하게 값을 넣어 준 것이다. 하지만 하지만 두번째 경우에는 복사한 c_list의 요소가 아닌 내부리스트에 .append()를 해준 것이기 때문에 내부리스트의 경우는 mutable하기 때문에 값이 같이 변화한 것을 알 수 있다. 즉, 얕은 복사의 경우 전체적인 복합객체(리스트 전체)만 복사되고 그 안의 내용은 동일하게 객체를 참조한다.
깊은 복사의 경우는 복합 객체를 새로 생성하고 그 안의 내용까지 재귀적으로 새롭게 생성한다. 참조 관계가 아니기 때문에 어느 쪽을 수정하던지 반대쪽에 영향을 끼치지 않는다.
import copy
a = [1, [1, 2, 3]]
b = copy.deepcopy(a)
print(b) # [1, [1, 2, 3]]
b[0] = 100
b[1].append(4)
print(b) # [100, [1, 2, 3, 4]]
print(a) # [1, [1, 2, 3]]