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) ### [[['라면'], '김밥'], '순대']
원본 리스트 [[['떡볶이'], '튀김'], '순대'] 가 [[['라면'], '김밥'], '순대']가 되었다.
- b[0][0][0]="라면"을 통해 참조 복사한 객체의 "떡볶이"를 "라면"으로 바꾸었다. 참조 복사로 인해 원본 객체의 떡볶이가 라면이 되었다.
- c[0][1]="김밥"을 통해 얕은 복사한 객체의 "튀김"을 "김밥"으로 바꾸었다. 얕은 복사로 인해 원본 객체의 튀김도 김밥이 되었다.
- d[1]="만두"를 통해 깊은 복사한 객체의 "순대"를 "만두"로 바꾸었다. 깊은 복사로 인해 원본 객체는 "순대"를 유지하게 되었다.
마무리
- 참조 복사와 얕은 복사, 깊은 복사의 차이를 이해하고
- 필요에 따라 copy 라이브러리를 활용하여
- 데이터의 독립성을 보장하고
- 코드의 안정성을 높이는
- 멋진 엔지니어가 되도록 하겠다 :)
'Code > function snippet' 카테고리의 다른 글
python | numpy 라이브러리를 이용하여 최대공약수, 최소공배수 구하기 (gcd, lcm) (0) | 2024.04.08 |
---|---|
python | 연속 공백 여러개 분리하기, split 함수 대신 정규식(re) 활용하기! (0) | 2024.04.08 |
python | 재귀함수 (팩토리얼 예제) (0) | 2024.04.01 |
python | 소수 판별 함수 작성하기 (prime number) (0) | 2024.04.01 |
python | 약수(factor) 리스트업 함수 작성하기 + 메모화 (memorization) (0) | 2024.04.01 |