참조(Reference)와 얕은 복사(Shallow copy), 깊은 복사(Deep copy)

김찬영·2024년 4월 18일
1

Python

목록 보기
1/4
post-custom-banner

데이터 전처리, 분석, 모델링 등등 내가 하는 모든 업무에 파이썬이 사용된다. 하지만 종종 "왜 이렇게 뜨지?"라며 의문이 들 때가 있었고, 코드 상의 문제도 있지만 많은 경우 파이썬 자체적인 이해가 필요한 부분이기도 했다. 그 중 하나가 참조의 영역이다.

참조는 파이썬을 처음 배울 때 정말 많이 접하는 단어이기도 하지만, 대개 '그냥 복제되는 거네'하고 넘어가는 경우가 대부분이다. 하지만 참조와 복사는 분명히 다른 개념이며, 간단히 짚고 넘어가고자 한다.

참조(Reference)

참조(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)로 나눌 수 있다.

  • 얕은 복사(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으로 변경됨.

결론적으로, 참조는 같은 객체에 대한 다른 경로를 제공하는 반면, 얕은 복사는 새로운 컨테이너 객체를 만들지만 그 내부 요소에 대한 참조는 원본 객체와 공유한다고 볼 수 있다.

profile
DS에 대한 고민과 해결을 글로 남기고자 합니다
post-custom-banner

0개의 댓글