[python3] 파이썬에서의 복사 - shallow copy & deep copy

손소·2022년 1월 1일
0

Python

목록 보기
1/6

파이썬에서 객체의 복사(copy)는 리스트의 자료형을 복사하는 경우에 적용하는 개념으로, 얕은 복사(shallow copy)깊은 복사(deep copy)가 있다. 객체의 종류에 따라 복사 방법에 차이가 있다.

1. mutable vs. immutable

복사에 대해 살펴보기에 앞서, 파이썬 객체의 종류에 대해 알아보자.
파이썬 객체는 크게 mutable과 immutable로 나눌 수 있다. mutable 객체는 변경 가능한 객체를, immutable 객체는 변경 불가능한 객체를 의미한다.

1) mutable 객체

  • list: 순서가 있는 객체 집합
  • set: 순서가 없는 고유한 객체 집합
  • dict: key와 value가 매핑된 객체, 순서X

2) immutable 객체

  • bool: True/False
  • int: 정수
  • float: 실수
  • tuple: 순서가 있는 객체 집합(immutable)
  • str: 문자열
  • frozenset: immutable한 set

3) 메모리 주소(id)

(1) 숫자형

x = 3
y = x
# x = 3, y = 3


이 경우 x와 y는 동일한 id를 갖고 있다.

하지만 y에 다른 값을 대입하는 순간 x와 y는 다른 id를 갖게 된다.

(2) 리스트

a = [1, 2, 3, 4] # 리스트는 mutable, 각 원소는 immutable(int형이므로)

리스트의 원소 값을 변경해도 리스트의 id는 동일하다. 하지만 값이 변경된 리스트의 원소의 id는 변경된다.

b = a # b와 a는 동일한 메모리 주소를 가지며, 원소들도 동일한 메모리 주소를 가짐.
b.append(7) # b에 7 추가

리스트 b에만 7을 추가했다. 리스트 a는 추가된 게 없어야 할 것이다.
하지만 리스트 a를 출력해보면, 리스트 b와 같이 7이 추가된 것을 확인할 수 있다.

리스트는 mutable한 객체이므로 리스트 a를 b에 할당한 후 리스트 b를 변경하면, 리스트 a에도 동일하게 적용된다. 이러한 리스트의 특성 때문에 의도치 않게 다른 객체까지 수정해버리는 문제가 발생할 수 있는데, 이 문제는 "복사(copy)"를 통해서 해결할 수 있다.

2. 얕은 복사(shallow copy)

  • list의 slicing을 통해 값을 할당하면서 다른 id를 갖게 하는 복사 방법
  • 새로운 id가 부여되므로, 서로 영향을 받지 않는다.
  • 각 list의 원소들은 같은 id를 갖고 있다.
a = [1, 2, 3, 4, 5]
b = a[:] # shallow copy

copy 라이브러리를 활용할 수 있다.

import copy

a = [1, 2, 3, 4, 5]
b = copy.copy(a) # [1, 2, 3, 4, 5]

When to use?

  • mutable한 객체가 immutable한 객체를 원소로 구성하는 경우

-> 그렇다면 mutable한 객체 내에 mutable한 객체가 존재하는 경우는?

import copy

a = [[1, 3], [2, 4]]
b = copy.copy(a) # [[1, 3], [2, 4]]
b[0][0] = 7
print(b) # [[7, 3], [2, 4]]
print(a) # [[7, 3], [2, 4]]

a도 똑같이 값이 변경되며, 동일한 id를 가진다.

mutable한 객체 내에 immutable한 원소가 있을 때는 얕은 복사로 객체 하나만 독립적으로 변경하는 것이 가능해졌지만 mutable한 객체 내에 mutable한 원소가 있을 때는 여전히 문제가 발생한다. 이는 "깊은 복사(deep copy)"를 통해서 해결할 수 있다.

3. 깊은 복사(deep copy)

  • mutable한 객체 내에 mutable한 객체가 포함되는 경우 사용
  • 객체 내의 mutable한 원소도 복사 시 다른 id를 가진다.
  • immutable한 원소는 동일한 값을 가지면 같은 id를 가진다.
import copy

a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0][0] = 7
print(b) # [[7, 2], [3, 4]]
print(a) # [[1, 2], [3, 4]] 

이 경우 a[0][1]과 b[0][1]은 값이 2로 같으며, immutable한 객체이므로 같은 id를 가진다.

직접 구현하기

def deepcopy(lst):
	
    """
    input: deepcopy를 하고자 하는 list lst
    output: deepcopy된 결과 list res
    """
	
    if type(lst[0]) == list: # lst가 행렬이라면
    	n = len(lst)
    	p = len(lst[0])
    	res = zero_mat(n, p)
    	for i in range(n):
    	    for j in range(p):
        	res[i][j] = lst[i][j]
    
    	return res
    else: # lst가 벡터라면
        n = len(lst)
        res = []
        for i in range(n):
            res.append(lst[i])
        return res

def zero_mat(n, p):

  """
  영행렬 생성
  input: 생성하고자 하는 영행렬의 크기, nxp
  output: nxp 크기의 영행렬 z
  """

    z = []
    for i in range(n):
        row = []
        for j in range(p):
            row.append(0)
        z.append(row)
    return z

0개의 댓글