데이터 전처리, 분석, 모델링 등등 내가 하는 모든 업무에 파이썬이 사용된다. 하지만 종종 "왜 이렇게 뜨지?"라며 의문이 들 때가 있었고, 코드 상의 문제도 있지만 많은 경우 파이썬 자체적인 이해가 필요한 부분이기도 했다. 그 중 하나가 참조의 영역이다.
참조는 파이썬을 처음 배울 때 정말 많이 접하는 단어이기도 하지만, 대개 '그냥 복제되는 거네'하고 넘어가는 경우가 대부분이다. 하지만 참조와 복사는 분명히 다른 개념이며, 간단히 짚고 넘어가고자 한다.
참조(Reference)는 특정 데이터를 가리키거나 '참조'하는 것을 말한다. 참조를 통해 데이터에 접근하고 수정할 수 있으며, 참조를 통한 수정은 원본 데이터에 직접 반영된다.
C언어에서의 포인터라고 생각하면 된다. 예를 들어 b = a 라고 했을 때, b는 a가 가리키고 있는 객체와 같은 객체를 가리키게 된다. 이후 b를 통해 객체를 변경하면 a를 통해 참조하는 객체도 변경된다. 즉, 하나의 객체에 두 개의 이름(참조)이 있는 것이다.
a = [1,2,3]
b = a # 참조 관계 형성
b[0] = 10 # b는 a와 동일한 리스트를 참조하고 있기 때문에, 이 변경은 a에도 영향을 미침
print(a)
>>> [10, 2, 3]
그럼 아래의 경우는 어떨까?
a = [1,2,3]
b = a
b = b + [10]
print(a)
>>> [1,2,3]
분명 참조의 개념을 사용하면 [1,2,3,10]이어야 한다. 하지만 b는 [1,2,3,10]이 되었을지언정 a는 [1,2,3] 그대로 유지되고 있다. 여기서 '+' 연산자의 역할을 파악해야 되는데, '+' 연산자로 인해 b에 새로운 리스트를 할당하게 되면서 a와 b가 서로 다른 객체를 참조하게 되는 것이다.
참조(Reference)는 특정 데이터를 가리키거나 '참조'하는 것이라고 했다. 반면, 복사(Copy)는 데이터의 독립적인 복제본을 만드는 것이다. 즉, 원본 데이터를 새로운 위치에 완전히 새로운 복제본을 만들어내는 것으로 생각하면 된다. 복사본은 원본과 동일한 값을 가지지만 서로 다른 메모리 주소를 가리키기 때문에, 복사본을 수정해도 원본에는 영향을 미치지 않는다.
# 복사의 예
original_list = [1, 2, 3]
copied_list = original_list.copy() # original_list의 복사본을 만듦
copied_list[0] = 10 # 복사본을 수정
print(original_list) # [1, 2, 3]
print(copied_list) # [10, 2, 3]
# 참조의 예
original_list = [1, 2, 3]
referenced_list = original_list # original_list를 참조
referenced_list[0] = 10 # 참조된 리스트를 수정
print(original_list) # [10, 2, 3]
print(referenced_list) # [10, 2, 3]
복사(Copy)는 얕은 복사(Shallow copy)와 깊은 복사(Deep copy)로 나눌 수 있다.
import copy
# 얕은 복사 예시
original = [[1, 2, 3], [4, 5, 6]]
shallow = copy.copy(original)
shallow[0][0] = 'changed'
# original[0][0]도 'changed'로 바뀜
# 깊은 복사 예시
original = [[1, 2, 3], [4, 5, 6]]
deep = copy.deepcopy(original)
deep[0][0] = 'changed'
# original[0][0]은 바뀌지 않음
이렇게 보면 참조와 얕은 복사가 매우 비슷해보인다. 참조와 얕은 복사를 비교하면서 포스팅을 마무리하도록 하겠다.
참조(Reference)
a = [1, 2, 3]
b = a # b는 a가 가리키는 리스트를 참조함.
b[0] = 10 # a와 b는 동일한 리스트를 가리키므로 a[0]도 10으로 변경됨.
앝은 복사(Shallow Copy)
a = [1, 2, [3, 4]]
b = a[:] # 얕은 복사를 통해 a의 최상위 리스트를 새로운 객체로 복사함.
b[2][0] = 10 # 내부 리스트 [3, 4]는 복사되지 않고 참조가 공유되므로 a[2][0]도 10으로 변경됨.
결론적으로, 참조는 같은 객체에 대한 다른 경로를 제공하는 반면, 얕은 복사는 새로운 컨테이너 객체를 만들지만 그 내부 요소에 대한 참조는 원본 객체와 공유한다고 볼 수 있다.