[Python] 얕은복사와 깊은복사 개념&예시 (shallow copy, deep copy)

반응형

 

Python Logo

 

* 전체 코드

## 얕은 복사
a=[1,2,3]
b=a[:] # b=a.copy()도 똑같이 동작
b.append(1)
print(f"{a=}, {b=}, {id(a)=}, {id(b)=}") 
# 출력: a=[1, 2, 3], b=[1, 2, 3, 1], id(a)=4379693696, id(b)=4379284288

## 얕은 복사의 한계
a=[[1,2],[3,4]]
b=a.copy() # 얕은 복사
a[0][0]=100
print(f"{a=}, {b=}, {id(a)=}, {id(b)=}") 
# 출력: a=[[100, 2], [3, 4]], b=[[100, 2], [3, 4]], id(a)=4378411776, id(b)=4378556096
print(f"{id(a[0])=}, {id(b[0])=}") # 출력: id(a[0])=4378554432, id(b[0])=4378554432

## 깊은 복사
import copy
a=[[1,2],[3,4]]
b=copy.deepcopy(a) # 깊은 복사 
a[0][0]=100
print(f"{a=}, {b=}, {id(a)=}, {id(b)=}") 
# 출력: a=[[100, 2], [3, 4]], b=[[1, 2], [3, 4]], id(a)=4378411648, id(b)=4378411968
print(f"{id(a[0])=}, {id(b[0])=}") # 출력: id(a[0])=4378412416, id(b[0])=4378411584

Contents

     


     

    1. 배경

    python에선 얕은 복사와 깊은 복사라는 개념이 있습니다. 이 개념이 중요한 이유는, 변수에서 변수로 값을 전달한 뒤 나중에 변수의 값을 변경했는데, 원치 않게 같은 메모리를 바라보는 다른 변수의 값도 변경될 수 있기 때문입니다.

    예를 들어보겠습니다.

    a=[1,2,3]
    b=a
    b.append(1)
    print(f"{a=}, {b=}")

    * 코드 설명

    a에 [1,2,3]이라는 리스트를 할당하고, b에 a를 할당한 뒤에, b에 1을 추가했습니다. 그 후, a와 b를 출력해보면 아래와 같이 a와 b 모두 [1, 2, 3, 1]로 출력됩니다. b에만 1을 추가했는데, 왜 a에도 1이 추가된 것일까요?

    * 출력

    얕은 복사가 필요한 이유

     

    그 이유는 b=a를 하는 순간, b와 a가 메모리에서 같은 주소를 가지게 되었기 때문입니다. id함수를 통해 메모리 주소를 출력해보겠습니다.

    메모리 주소 출력 -> a 와 b가 같다

    id(a)와 id(b)의 값이 같습니다. 메모리 주소가 같다는 건, 이름만 다르지 내용은 똑같다는 의미입니다. 원인은 python의 변수 할당 방식에 있습니다. 간단히 말하면, python의 mutable 변수(list, dict, set)는 b=a처럼 변수간 대입을 했을 때, 같은 메모리 주소를 바라보도록 설계되어있습니다. 변수간 대입을 해도 b를 변경했을 때, a가 변경되지 않게 하려면 얕은 복사와 깊은 복사의 개념을 알아야 합니다.

     

    2. 얕은 복사(shallow copy)

    a=[1,2,3]
    b=a[:] # b=a.copy()도 똑같이 동작
    b.append(1)
    print(f"{a=}, {b=}")
    print(f"{id(a)=}, {id(b)=}")

    b=a를 b=a[:]로만 바꿔보았습니다. 이를 슬라이싱을 이용한 얕은 복사라고 합니다. b=a.copy()를 해도 동일하게 동작합니다. 

    * 출력

    얕은 복사

    결과를 보면 b에만 1이 추가되었고, a는 그대로입니다. 그 이유는 변수가 가리키는 메모리 주소가 서로 달라졌기 때문입니다. id 출력 결과 a의 메모리 주소와 b의 메모리 주소가 달라졌습니다. 하지만 얕은 복사라는 말처럼 여기에는 한계가 있습니다.

     

    3. 얕은 복사의 한계

    a=[[1,2],[3,4]]
    b=a.copy() # 얕은 복사
    a[0][0]=100
    print(f"{a=}, {b=}")
    print(f"{id(a)=}, {id(b)=}")
    print(f"{id(a[0])=}, {id(b[0])=}")

    이번엔 a를 중첩 리스트(nested list)로 만들었습니다. 그 후, 얕은 복사로 b에 변수간 대입을 하였고, a[0][0]=100을 통해, a 내부 첫 번째 리스트 [1,2]에서 첫 번째 원소인 1을 100으로 변경했습니다. 그리고 a와 b를 출력해보겠습니다.

    * 출력

    얕은 복사의 한계

    a의 값만 변경했는데, b[0][0]도 100으로 변경되었습니다. id(a)와 id(b)의 결과를 보면, 변수 a와 변수 b가 가리키는 메모리 주소도 서로 다른데 말입니다. 왜 그런 것 일 까요? 맨 아래 빨간 박스 부분을 보면 a[0]과 b[0]이 가리키는 주소는 똑같다는 것을 알 수 있습니다. 즉, python에서 mutable 변수 내부에 또 mutable이 있는 경우, 얕은 복사로는 mutable 내부의 mutable의 메모리 주소는 달라지지 않는다는 것입니다.

     

    만약 코딩을 하는데, 중첩 리스트이거나 중첩 딕셔너리 등의 복잡한 구조를 가진 mutable 변수에 대해 변수간 대입을 하는 경우, 얕은 복사로는 변수간 독립성이 보장되지 않습니다. 이럴 때 필요한 것이 깊은 복사입니다.

     

    4. 깊은 복사(deep copy)

    import copy
    a=[[1,2],[3,4]]
    b = copy.deepcopy(a)
    a[0][0]=100
    print(f"{a=}, {b=}")
    print(f"{id(a)=}, {id(b)=}")
    print(f"{id(a[0])=}, {id(b[0])=}")

    위와 마찬가지로 a는 중첩 리스트입니다. 바뀐 점은 copy 모듈을 import 했고, b = copy.deepcopy(a)를 통해 깊은 복사를 했습니다. a[0][0]을 100으로 바꾼 뒤, 출력해보겠습니다.

    * 출력

    깊은 복사

    결과를 보면, b[0][0]은 변경되지 않고 독립성이 유지된 것을 볼 수 있습니다. 변수의 메모리 주소를 보면 a와 b의 메모리 주소도 다르고, 빨간 박스 친 부분을 보면 a[0]과 b[0]의 메모리 주소도 다릅니다. 즉, 중첩된 mutable 변수에 대해서도 완전히 독립성이 유지된 것을 확인할 수 있습니다.

     

    4. 결론

    python 코딩을 진행할 때, immutable(int, float, str, tuple, bool 등)은 애초에 값이 바뀌지 않으니 변수간 대입을 해도 신경 쓰지 않아도 됩니다. 하지만 값이 바뀌는 변수(mutable)는 변수 간 대입시 중첩되었는지 유무에 따라 얕은 복사 혹은 깊은 복사를 써야 변수간 독립성이 보장됩니다. 사실 코딩을 하다 보면 깊은 복사는 거의 쓰지 않고, 대부분 얕은 복사선에서 변수간 독립성이 보장됩니다. 그래도 깊은 복사의 개념은 알아두는 게 좋을 것 같습니다.

     

    이상으로 얕은 복사와 깊은 복사에 대한 포스팅을 마치겠습니다.

    읽어주셔서 감사합니다.

    다음에 더 재미있고 유익한 글로 찾아뵙겠습니다.

     

    * 위의 실습 코드는 아래의 깃허브에 모두 정리해두었습니다.

     

    GitHub - netsus/python_practice: basic python course

    basic python course. Contribute to netsus/python_practice development by creating an account on GitHub.

    github.com

     

    Reference)
    python logo: https://commons.wikimedia.org/wiki/File:Python_logo_and_wordmark.svg
    얕은 복사, 깊은 복사: https://wikidocs.net/16038
    반응형

    댓글

    Designed by JB FACTORY