본문 바로가기

Python

[Python] 얕은 복사, 깊은 복사

앝은 복사, 깊은 복사


파이썬은 객체지향 언어다. 이부분이 참 낯설고도 아쉽다면 아쉬운 부분인게, 프로그래밍 초심자에게 파이썬을 알려줄 때, 이 객체지향이라는 말은 최대한 쓰지않는다. 너무 어렵기도하고, 또 파이썬을 "어느정도" 다룰때는 몰라도 되는 개념이니까. 내 생각에 파이썬은 컴파일 언어가 아닌 인터프리터 언어라 인간의 문법대로 슥슥 코딩이 가능하니 더 그러한 것 같다. 하지만 파이썬은 여전히 객체지향 언어이다. 절차지향대로 코딩하여도 큰 문제는 없지만 해결되지 않는 여러 의문점이 들 수 있다. (이런 의문이 든다면 훌륭한 개발자가 될 수 있다. 그니까 나는 훌륭한 개발ㅈ...) 하나씩 해결해 보자.


파이썬은 객체지향 언어이다.


  • 파이썬에서는 데이터도 객체다.
    • 변수에 데이터가 저장된다. (x)
    • 변수가 데이터를 가리킨다. (o)
      • ex) 리스트의 얕은, 깊은 복사 (.copy())

여기서 얕은 복사(shallow copy)와 깊은 복사(deep copy)개념이 등장한다. 우리가 무심코 선언한 모든 변수는 전부 "객체"다. (!!) 파이썬의 객체에는 mutable 객체와 immutable 객체가 있다. 아래의 표를 살펴보자.


class 설명 구분
list mutable 한 순서가 있는 객체 집합 mutable
set mutable 한 순서가 없는 고유한 객체 집합 mutable
dict key와 value가 맵핑된 객체, 순서 없음 mutable
bool 참,거짓 immutable
int 정수 immutable
float 실수 immutable
tuple immutable 한 순서가 있는 객체 집합 immutable
str 문자열 immutable
frozenset immutable한 set immutable

출처


위에서 설명했듯, 변수는 그 자체가 데이터가 아니라 "데이터가 저장된 메모리의 주소"를 가르킨다. 변수가 선언되면 해당되는 자료형의 크기 만큼 메모리가 할당되고 변수는 할당된 메모리를 가리키는 이름이라고 생각하자.


여기까지 이해가 되었다면, mutable과 immutable에 대해 간단히 알아보자. mutable과 immutable은 뜻 그대로, 'mutable: 변경할 수 있는, immutable: 변경할 수 없는'이다. 그러니까, 숫자, 문자열, 튜플은 값을 변경하지 못하고, 리스트와 딕셔너리는 변경할 수 있다는 뜻이다. 뭔소리지? 자세히 알아보자.


1. immutable: 클래스의 인스턴스가 일단 생성된 후에는 인스턴스의 내용이 절대 변하지 않음


int_a = 1
id(int_a) 

immutable객체인 int_a가 선언되었다. 이때 메모리 주소를 찍어보니, 1413602634032이다. int_a의 값을 바꿔보면 어떻게 될까?


int_a = 2
id(int_a) # 1413602634064

1413602634064로 바뀐걸 볼 수 있다. 똑같은 int_a인데 메모리 주소가 다르다. 즉 immutable 객체는 "메모리에 어딘가 값이 저장되어 있고, 변수는 그 주소를 가르킨다"라고 이해하면 이해가 쉽다. 그럼 이를 복사할 경우 어떻게 될까?


int_a = 1
id(int_a) # 1413602634032

int_b = int_a
id(int_b) # 1413602634032

int_b에 int_a의 값을 할당했다. 같은 값을 가르키니 당연히 메모리 주소도 같을 것이다. 이제 int_b에 다른 값을 넣어보자.


int_a = 1
id(int_a)

int_b = int_a
id(int_b)

int_b = 2

print(int_a, id(int_a)) # 1 1413602634032
print(int_b, id(int_b)) # 2 1413602634064

int_b가 처음에 int_a의 값을 할당받아 선언 되었다고 하더라도, 값이 변하면 메모리 주소도 변하고, int_a != int_b는 참이 될 것이다. 아주 상식적이다. 아주 상식적이다. 이제 mutable 객체를 살펴보자.


2. mutable: 인스턴스가 생성된 후에 값의 내용이 변할 수 있는 클래스, 주소는 못 바꾼다.


list_a = [1, 2, 3]
id(list_a) # 1413689081408

여기 list_a가 선언되었다. 메모리 주소는 1413676259072이다. 자, 여기서 list_a의 값을 변경해 보자.


list_a.append(4)
id(list_a) # 1413689081408

짜잔~ 값이 변해도 메모리 주소는 동일하다!! 즉 한번 선언된 인스턴스는 값은 변해도 주소는 변하지 않는다.


이제 list_b에 list_a를 할당해보자.


list_b = list_a
id(list_b) # 1413689081408

자 여기까지는 상식적이다. 이때 list_b의 값을 바꾼다면?


list_b.append(5)

print(id(list_a), list_a) # 1413689081408 [1, 2, 3, 4, 5]
print(id(list_b), list_b) # 1413689081408 [1, 2, 3, 4, 5]

놀랍게도 list_a의 값마저 바뀌며, 주소는 변하지 않는다. 이를 "얕은 복사"라고 부른다. 변수는 메모리 주소를 가르킬 뿐이다. 그럼 깊은 복사는 뭘까?


list_c = list_a.copy()

print(id(list_a), list_a) # 1413689081408 [1, 2, 3, 4, 5]
print(id(list_c), list_c) # 1413689088960 [1, 2, 3, 4, 5]

.copy() 메소드를 이용하여 메모리 주소를 변경해 주는 것이 "깊은 복사"이다. 메모리 주소는 바뀌었지만, 값은 그대로이다.


즉 mutable 객체는 한번 선언되면 메모리 주소가 변하지 않고, 값은 변할 수 있다.

  • immutable은 "메모리 어딘가에 이미 저장된 데이터를 변수가 가르키는 것"이라고 이해했다면,

  • mutable 객체는 "선언되는 순간, 메모리 어딘가에 공간을 확보하고, 그 변수는 그 주소를 가르키며 값은 변할 수 있다라고" 이해하자.


ps.

list_a = [1, 2, 3]
print(id(list_a)) # 1413676298624

list_a = [1, 2, 3]
print(id(list_a)) # 1413676299776

list_a = [1, 2, 3]
print(id(list_a)) # 1413676299776

list_a = [1, 2, 3]
print(id(list_a)) # 1413676298624

당연하게도, mutable객체는 같은 값의 같은 변수명이라도 재선언할 때마다 메모리 주소가 변경된다.


a = 1
print(id(a)) # 1413602634032

a = 1
print(id(a)) # 1413602634032

a = 1
print(id(a)) # 1413602634032

a = 1
print(id(a)) # 1413602634032

immutable객체는 이미 저장된 데이터의 주소를 가르키는 것이니 재선언하였다해서 주소가 바뀌는 일은 없다.

'Python' 카테고리의 다른 글

[Python] 파이썬 동시성 프로그래밍 (1)  (0) 2022.02.07
[Python] Module 정리  (3) 2021.09.13
[Python] 모국어같은 파이썬... & Class 정리  (1) 2021.09.13