from Karte,

파이썬에서의 deepcopy 와 shallow copy feat. copy module 본문

study log/Python

파이썬에서의 deepcopy 와 shallow copy feat. copy module

karte 2020. 10. 4. 22:38

* 파이썬 전문가가 작성한 글이 아니라 내용 오류가 있을 수도 있는 점 유의 바랍니다. 혹시 오류 발견 시 댓글로 알려주시길 부탁드립니다. 

* 포스팅 작성 시 참고한 소스들은 아래 References 에 기재하였으니 원문 확인이 필요하신 분들은 참고해 주시면 됩니다. 

 

 

누군가에겐 너무나도 기본적인 내용일 수도 있지만, 나는 파이썬을 프로그래밍 언어로서 접하지 않고 데이터 분석 언어로서 처음 접했다. 그러다 보니 이제까지는 CS ( Computer Science ) 적인 이야기가 나오면 주춤하곤 했는데, 이제는 그러기 싫어서 파이썬 카테고리를 새로 생성했다. 앞으로 프로그래밍 관점에서의 파이썬 토박 상식을 주로 포스팅 할 예정이다.

 

파이썬 카테고리의 올라갈 첫 번째 글은 바로 파이썬에서의 객체 복사 ( copy ) 와 관련된 글이다. 연습 목적이든 대회 출전의 목적이든 보통 데이터 분석을 하면 원본 데이터는 적재한 상태 그대로 두고, 원본 데이터의 복사본을 만들어 여러 가지 피처들을 테스트 해보려고 하게 되는데, 그럴 때 필요한 copy 모듈과 파이썬에서의 copy 관련 개념을 정리해 보려 한다. 

 


Assignment Operator ( = , 할당 연산자) vs Shallow Copy (얕은 복사) vs Deep Copy ( 깊은 복사 ) ?

 

파이썬에서 이미 정의된 객체를 복사할 수 있는 - 혹은 표면적으로는 복사를 한 것처럼 보이는, 그러나 실제로는 절대 아닌 -  방법에는 3가지가 있다. 할당 연산자 = 을 쓰거나, shallow copy 를 하거나 아니면 deepcopy 를 하거나! 하나씩 알아보자. 

 

1. 할당 연산자 ( = )

 

아래 코드 블럭에서 보이는 것처럼 이미 정의된 변수를 새로운 변수에 할당하는 것이다. 

a = ['orange', 500, 'kiwi', 450, ['pitaya', 'dragon fruit'], 1000] # 과일 이름과 가격이 저장된 리스트

b = a # 할당 연산자로 새로운 변수 b 에 a 를 저장

그런데 중간에 오렌지 가격이 600 으로 올라서 변수에 반영을 하되, 이전 가격은 a 에 그대로 저장을 하고 b 의 오렌지 가격만 변경을 해주어야 하는 상황이 있다고 억지스럽지만 가정하면, 

b[1] = 600 # b에서만 오렌지 가격 변경

위와 같이 코드를 입력하면 되는 게 아닌가? 라고 생각을 할 수도 있다. ( 나도 처음에는 그랬다 ) 그렇지만 아니다!

할당 연산자를 저렇게 쓰면 a 객체를 복사하고, 그 값을 다시 b 에 저장하는 방식으로 동작할 것 같지만 그냥 그렇게 보일 뿐이고, 실제로는 전혀 아니다. 결과를 보면 a 변수의 값 또한 같이 변해있을 것이다. 

 

할당 연산자의 역할은 변수와 값을 묶어주는 ( binding ) 일을 한다. 조금 더 자세히 말하면, 원래 a 변수가 가리키고 있던 위의 과일 이름과 가격이 저장된 리스트의 메모리 주소를 b 변수도 가리킬 수 있게 해주는 것이다. ( '가리키다' 라는 단어가 C 언어의 포인터를 연상되게 해서 되도록 쓰고 싶지 않았으나 달리 더 좋은 표현이 떠오르지 않았다...파이썬에는 포인터 개념이 없음에 주의! ) 따라서 b = a 를 실행 후 b 변수에서 인덱싱을 통해 다른 값을 입력하려고 한다면 a 변수의 값도 같이 변하게 되는 것이다. 그림으로 표현하면 아래와 같다. 

 

이미지 출처: karte 본인 작성

 

 

2. Shallow copy & Deep copy ( copy 모듈 임포트해야 사용 가능한 복사 방식 )

 

shallow copy 와 deep copy 를 구분하는 기준은 '복합객체를 복사할 때의 과정이 재귀적인가?' 이다. shallow copy 는 복사 과정이 재귀적이지 않은 반면, deep copy 는 재귀적이다.

참고: 구글링을 해보니 list, dict, set 과 같이 객체 내부에 또 다른 객체를 담을 수 있는 자료형을 복합객체 ( compound object ) 라고 부른다고 한다. 

 

shallow copy 는 할당 연산자와 달리 새로운 객체를 생성은 하되, 내부의 요소들은 기존의 객체의 원소들을 참조한다. 한 마디로 말하자만 껍데기는 새로 생성하되 알맹이들은 새로 생성하지 않고 기존에 선언되어 저장된 메모리 상의 위치를 참조하는 것이다.아래 References 의 4번 문서에서는 이 같은 성질을 'one level deep' 이라고 표현했다. 즉, list of lists 와 같은 구조일 경우 가장 바깥의 list 껍데기 ( one level deep )는 새로 생성이 되지만, list 내의 list 는 그렇지 않으므로 변수 b 에서 ['pitaya', 'dragon fruit'] 의 값을 바꾸면 a 에서도 값이 변한다. shallow copy 동작을 그림으로 표현하면 다음과 같다. 

 

이미지 출처: karte 본인 작성

 

이제 shallow copy 를 구현하는 코드를 보자!

import copy # shallow copy / deep copy 구현을 위해 필요한 모듈

a = ['orange', 500, 'kiwi', 450, ['pitaya', 'dragon fruit'], 1000] # 과일 이름과 가격이 저장된 리스트

b = copy.copy(a) # copy 모듈의 copy 함수로 기존의 a 변수를 shallow copy 가능

shallow copy 를 한 상태에서 b 변수의 값을 일부만 변경시킨 후 a 변수를 확인하면 아래 캡처와 같다.

 

이미지 출처: karte 본인

b 변수를 shallow copy 하면서 one level deep 했던 가장 바깥의 [] 와 원소들은 다른 객체로서 생성이 되어 b[0] = 'berries' 를 실행해도 a 변수에는 변함이 없으나, 내부의 [ 'pitaya', 'dragon fruit'] 까지는 새로 생성이 되지 않아 b[4][1] = '용과' 라고 값 변경을 하니 a 변수에도 변경사항이 반영이 된 것을 알 수 있다.

 

 

반면 deep copy 는 복사 과정이 재귀적이다. 기존의 복합 객체를 복사하여 완전히 다른 객체를 생성한다. 아래 그림에서 보는 것처럼 기존의 변수와 값은 같으나 완전히 다른 객체를 생성해 준다. 흔히 상상하는 copy 의 이미지와 동일한 동작을 수행하는 것이라는 생각이 든다. 

 

이미지 출처: karte 본인

 

 

구현은 아래와 같이 copy 모듈의 deepcopy 함수를 쓰면 된다.

import copy # shallow copy / deep copy 구현을 위해 필요한 모듈

a = ['orange', 500, 'kiwi', 450, ['pitaya', 'dragon fruit'], 1000] # 과일 이름과 가격이 저장된 리스트

b = copy.deepcopy(a) # copy 모듈의 deepcopy 함수로 기존의 a 변수를 deep copy 가능

 

다음과 같이 b 변수의 값의 일부를 변경시켜도 a 변수에는 반영이 안되는 것을 확인할 수 있다.

 

이미지 출처: karte 본인

 

 

이 때문에 개인적으로 데이터 분석을 할 때는 deepcopy 를 많이 쓸 것 같다. 


이걸로 첫 파이썬 관련 개념 정리 포스팅을 마치려고 한다. 내용적인 면이나 형식 면에서 아쉬움이 없지는 않으나 이제 겨우 첫 술을 들이킨 셈 치고...아직 마실 술이 많기에 벌써 배부르면 안된다는 생각으로 포스팅을 마친다. 

 

 

 

References

 

1. GeeksforGeeks : Copy in Pyhton ( Deep Copy and Shallow Copy )

2. Python 3 Documentation : copy module     

3. Programiz : Python Shallow Copy and Deep Copy      

4. Real Python : Shallow vs Deep Copying of Python Objects  

Comments