Python에서는 변수와 객체가 메모리에 어떻게 저장되는지에 대해 처음부터 깊이 배우는 경우가 많지 않다.

나도 예전에 풀스택 쪽을 잠깐 본 적이 있었는데, 그 때 JavaScript(자바스크립트)를 배우면서 메모리 할당과 데이터 저장 방식에 대해 배우게 되었다. JavaScript는 원시 데이터 타입과 참조 타입을 구분하고, 얕은 복사와 깊은 복사의 차이도 눈에 띄게 다르게 작용하기 때문에 메모리 참조에 대해 더 많은 주의가 필요하다.

그러나 Python은 메모리 관리를 자동으로 처리해 주고, 변수와 객체는 직관적으로 사용할 수 있도록 설계되었기 때문에, 복잡한 메모리 관리를 사용자가 직접 다룰 일이 거의 없고, 따라서 초보자가 쉽게 접근할 수 있는 편이다.

이러한 차이는 언어의 철학과 설계 방식에서도 기인한다고 한다. JavaScript는 클라이언트 측에서 웹 성능과 자원을 관리해야 하므로 메모리 참조와 관련된 개념을 초반에 배워야 하는 경우가 많고, 파이썬에서는 성능 최적화보다는 코드의 가독성과 간결함을 중요시하는 경향이 있기 때문에, 메모리 관리에 대한 부담이 상대적으로 적게 느껴질 수 있다.

본 포스팅에서는 참조복사/얕은복사/깊은복사의 차이점을 다루고, 파이썬의 copy 라이브러리를 통해 각 복사의 차이점을 구현해 보도록 하겠다.


파이썬 기본 참조 복사

# 원본 리스트
a = ["떡볶이", "튀김", "순대"]

# a를 b에 복사
b = a

# b의 첫 번째 항목을 변경
b[0] = "라면"

# 결과 확인
print("a 리스트:", a)  # ['라면', '튀김', '순대']
print("b 리스트:", b)  # ['라면', '튀김', '순대']

b=a 구절에서 b가 a와 같은 메모리 위치를 참조하기 때문에 a, b는 실제로는 같은 객체가 된다.


  •  

copy 라이브러리

Python의 copy 라이브러리는 객체의 얕은 복사(shallow copy) 깊은 복사(deep copy)를 지원하는 유용한 라이브러리로, 특히 다차원 리스트나 중첩된 데이터 구조를 복사해야 할 때 유용하게 사용할 수 있다.

copy와 deepcopy의 차이점

copy 라이브러리에는 copy()와 deepcopy() 두 가지 주요 기능이 있다.

  • copy.copy(): 얕은 복사로, 최상위 레벨의 객체만 복사함. 즉, 리스트 안의 리스트와 같은 중첩 객체의 경우, 내부 객체는 같은 메모리 참조를 가지게 된다.
    • a를 복사해서 b를 만들었는데, b를 수정하면 a도 같이 수정됨
  • copy.deepcopy(): 깊은 복사로, 모든 객체의 중첩 레벨을 따라가며 재귀적으로 새 객체를 생성하여 완전히 독립적인 복사본을 만든다. 따라서 복사된 객체가 원본 객체와 독립적으로 수정될 수 있다.
    • a를 복사해서 b를 만들었는데, b를 수정해도 a가 변하지 않고 원본을 유지함

얕은 복사(shallow copy)

얕은 복사(shallow copy)는 새로운 객체를 생성하지만, 그 객체 안에 들어 있는 요소들은 원본 객체와 같은 참조를 가리킨다. 즉, 리스트의 최상위 레벨은 복사되지만, 내부의 중첩된 객체들은 원본을 참조하기 때문에.

깊은 복사(deepcopy)의 필요성

 

  • 리스트, 딕셔너리 등 다차원 데이터를 복사할 때 원본 데이터의 변경이 복사본에 영향을 주지 않도록 해야 할 때 - 즉, 원본 객체를 보호하고자 할 때 사용(deepcopy)
    • 작업 중간에 데이터를 보존하여 나중에 초기 상태로 복원해야 할 때 deepcopy를 사용하면 데이터 일관성을 유지할 수 있음
  • 특히 다수의 객체가 동일한 데이터를 참조하는 상황에서 유용

예제 코드

import copy

# 원본 리스트
a = [[["떡볶이"], "튀김"], "순대"]

b = a                 # 1. 참조 복사
c = copy.copy(a)      # 2. 얕은 복사
d = copy.deepcopy(a)  # 3. 깊은 복사

# 복사 방식의 차이 확인
b[0][0][0] = "라면"   # 참조 복사한 b의 첫 번째 요소의 첫 번째 리스트를 수정
c[0][1] = "김밥"      # 얕은 복사한 c의 첫 번째 리스트의 두 번째 요소를 수정
d[1] = "만두"         # 깊은 복사한 d의 두 번째 요소를 수정

# 결과 출력
print("원본 리스트 a:", a)  ### [[['라면'], '김밥'], '순대']

원본 리스트 [[['떡볶이'], '튀김'], '순대'] 가 [[['라면'], '김밥'], '순대']가 되었다.

  1. b[0][0][0]="라면"을 통해 참조 복사한 객체의 "떡볶이"를 "라면"으로 바꾸었다. 참조 복사로 인해 원본 객체의 떡볶이가 라면이 되었다.
  2. c[0][1]="김밥"을 통해 얕은 복사한 객체의 "튀김"을 "김밥"으로 바꾸었다. 얕은 복사로 인해 원본 객체의 튀김도 김밥이 되었다.
  3. d[1]="만두"를 통해 깊은 복사한 객체의 "순대"를 "만두"로 바꾸었다. 깊은 복사로 인해 원본 객체는 "순대"를 유지하게 되었다.

마무리

  • 참조 복사와 얕은 복사, 깊은 복사의 차이를 이해하고 
  • 필요에 따라 copy 라이브러리를 활용하여
  • 데이터의 독립성을 보장하고
  • 코드의 안정성을 높이는
  • 멋진 엔지니어가 되도록 하겠다 :)

 

 

+ Recent posts