Intro

 

  • shallow copy와 deep copy는 모든 분야에 통틀어 알고 있어야 하는 지식이다.
  • Python은 모든 걸 객체취급하는데, 이 객체의 복사를 수행하는 명령어가 copy 명령어다.
    • 어느 수준까지 복사가 되는 지에 따라 copy, shallow 그리고 deep copy로 나눠진다.
    • 각 copy에 대해 정확히 이해한 후, 프로그램 개발에 사용해야 문제가 발생하지 않고, 디버깅의 방해 요소가 되지 않는다.
    • 또한, 구현하려는 목적에 맞게 이 3가지 copy 방식을 구분해서 사용해야 한다.

 


1. Copy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# mutable data type
> a_list = [1, 2, 3, [4, 5, 6], [7, 8, 9]]

# call by reference
> b_list = a_list

# 동일한 id가 출력된다.
> print(id(a_list), id(b_list))
2230595359488  2230595359488

> print(id(a_list) == id(b_list))
True

# b_list를 수정해보자.
> b_list[2] = 100

# b_list만 수정했지만, 어째서인지 a_list까지 수정되었다.
> print(a_list)
[1, 2, 100, [4, 5, 6], [7, 8, 9]]

> print(b_list)
[1, 2, 100, [4, 5, 6], [7, 8, 9]]

# b_list를 다시 수정해보자.
> b_list[3][2] = 100

> print(a_list, b_list)
[1, 2, 100, [4, 5, 100], [7, 8, 9]]

> print(a_list, b_list)
[1, 2, 100, [4, 5, 100], [7, 8, 9]]
  • 일반적인 copy 방식으로 call by reference 방식이다.
  • 동일한 reference를 참조하기 때문에, b_list의 성분만을 수정했지만 a_list 까지 수정되었다.

 


2. Shallow Copy

중첩 data는 수정된다.

  • shallow copy는 위에 일반 copy와 달리 copy module을 import 해야 한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    > c_list = [1, 2, 3, [4, 5, 6], [7, 8, 9]]
    > d_list = copy.copy(c_list)
    
    # 위에 call by reference로 복사한 것과 달리 id 값이 다른 걸 확인할 수 있다.
    > print(id(c_list), id(d_list))
    1892474493824  1892474493568
    
    # d_list 수정
    > d_list[1] = 100
    
    # c_list는 수정되지 않았다.
    > print('c_list > ', c_list)
    c_list > [1, 2, 3, [4, 5, 6], [7, 8, 9]]
    
    # d_list만 수정되었다.
    > print('d_list > ', d_list)
    d_list > [1, 100, 3, [4, 5, 6], [7, 8, 9]]
    
  • 여기까지만 보면 왜 shallow copy인지 이해가 안갈 것이다.

  • 왜냐하면 d_list를 수정해도 c_list가 수정되지 않기 때문이다.

  • 그러면 list 안에 list 성분을 수정해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    > d_list[3].append(1000)
    > d_list[4][1] = 10000
    
    # d_list만 시도했지만, c_list까지 중첩 data가 수정되었다.
    > print('c_list > ', c_list)
    [1, 2, 3, [4, 5, 6, 1000], [7, 10000, 9]]
    
    > print('d_list > ', d_list)
    [1, 100, 3, [4, 5, 6, 1000], [7, 10000, 9]]
    
  • mutable 안에 중첩 data는 동일한 reference를 참조한다는 걸 알 수 있다.

  • 이를 id function으로 확인해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    
    > print('nested data - ',id(c_list[3]), id(d_list[3]))
    nested data -  2343545032000 2343545032000
    
    > print('c_list > ', id(c_list))
    c_list >  2636124593152
    
    > print('d_list > ', id(d_list))
    d_list >  2636124592896
    
  • 중첩된 data 까지는 독립된 reference를 가지지 않는 걸 확인했다.

 


3. Deep Copy

중첩된 data까지 독립된 id를 가진다.

  • Deep copy도 copy module을 import 하는 것부터 시작한다.

  • shallow copy는 중첩 성분을 포함하는 객체만 복사하는 방식이면, deep copy는 중첩 성분까지 복사한다.

  • 그래서 deep copy를 깊은 복사 말고, 전체 복사라고도 한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    > c_list = [1, 2, 3, [4, 5, 6], [7, 8, 9]]
    > d_list = copy.deepcopy(c_list)
    
    # 다른 id 값을 확인할 수 있다.
    > print('Ex3 > ', id(c_list))
    Ex3 >  2636124592960
    > print('Ex3 > ', id(d_list))
    Ex3 >  2636124593408
    
    # 내장된 data도 독립된 id값을 가진 걸 확인할 수 있다.
    > print('nested data > ', id(c_list[3]))
    nested data >  1329303493056
    
    > print('nested data > ', id(d_list[3]))
    nested data >  1329303494272
    

 


4. list comprehension에서를 통한 깊은 복사

list comprehension의 주의사항 글을 보면 리스트를 곱셈 연산을 사용하여 늘리는 것과 list comprehension으로 늘리는 것은 다르다는 걸 알 수 있는데, 곱셈 연산은 똑같은 참조값을 참조하는 값을 늘리는 것으로 얕은 복사(shallow copy)다. 하지만 list comprehension으로 늘리는 것은 동일한 값이지만 다른 참조값을 가지는 걸 통해 ‘객체를 새로 생성하는 것’으로 늘리므로 깊은 복사(deep copy)임을 알 수 있다.


5. Summary

  • 다음 image로 shallow copy와 deep copy 복사 정도를 비교하면 쉽게 알 수 있다.

  • 같은 색상의 block이 동일한 id를 가지고 있다.

  • shallow copy는 객체만을 복사하고, 객체의 성분은 복사하지 않는다.

    • 객체는 call by value, 객체의 성분은 call by reference
  • 하지만, deep copy는 객체와 객체의 성분까지 복사한다.

    • 객체와 객체의 성분도 call by value
  • shallow copy image

  • deep copy

    image

 


Reference