Python

[Python] 파이썬 - 06. 대입연산, 얕은 복사, 깊은 복사

Hoody Coder 2023. 3. 11. 21:31

 

  이번 게시글에선 id(), copy(), deepcopy()를 활용하여 단순복사, 얕은 복사(shallow copy), 깊은복사(deep copy)에 대해 알아보자.

그전에 대입연산 에대해서 알아보자

 

1) 대입연산

  대입연산은 주소값을 복사하여 대입하는 것을 의미한다. 

아래의 코드를 보자. 여기서 id() 는 변수의 주소값을 반환한다.

var = 5
id_var = id(var)
print(id_var)

copy_var = var
id_copy_var = id(copy_var)
print(id_copy_var)

print( id_var == id_copy_var)

  대입연산의 포인트는 copy_var = var 에서 복사하는것이 var의 주소값이 라는 것이다. 

따라서 위의 결과와 같이 두 변수의 주소값은 같으며, 해당하는 값 역시 같다는 것을 알 수 있다. 

 

그럼 위의 상태에서 a또는 b의 값을 바꾸게 되면 어떻게 될까?

파이썬의 int타입은 immutable한 타입이다. immutable하다는 것은 수정불가능하다는 뜻이다. 

즉 파이썬에서 int타입은 값 별로 독립적인 메모리 공간을 가지고 있다는 뜻이다. 

위의 코드에 이어서 만약 var의 값을 바꾸면 어떻게 될까?

var = 33
print(var)
print(copy_var)

var이 33으로 바뀌면서 var은 33을 가르키는 메모리 공간으로 수정되었다. 

따라서 var과 copy_var는 각자 다른 메모리공간을 바라보게되면서, 서로 다른 값을 나타내게 된 것이다. 

 


2) 얕은 복사(Swallow Copy)

   얕은 복사는 새로운 인스턴스를 생성하고, 해당 인스턴스의 값만 복사하는 특징이 있다.

대표적인 메서드로는 copy()가 있다. 

x = [1, 2, 3, 4, 5]
copy_x = x # 대입 연산
copy_x2 = x.copy() # 얕은복사

print(x, copy_x, copy_x2)
print(id(x), id(copy_x), id(copy_x2))

copy_x는 x에 대해서 대입을 통한 복사이고, copy_x2는 x에 대해서 얕은 복사를 한것이다. 

단순히 값만 비교했을때, 세 변수의 값은 모두 동일하지만, 대입을 통해 복사한 copy_x는 메모리주소값까지 x와 동일한 것을 볼 수 있다.

즉 얕은 복사를 사용한다는 것은 원본의 값이 변하더라도, 복사를 통해 가져온 변수들은 기존과 같은 값을 유지하는 것임을 추론하는것이 가능하다. 

일반적으로 파이썬에선 리스트의 값을 복사할때 슬라이싱[:] 을통해서 값을 복사하는 경구가 많은데, 이 역시 얕은 복사에 해당한다. 

 그런데 이런 얕은복사를 잘못쓰면 원치 않는 결과를 얻을 수도 있다. 

아래와 같은 중첩리스트를 예로 보자

아래의 사용한 copy.copy() 역시 얕은 복사를 할수있는 방법중 한가지이다.

import copy

a = [1, [2, 3, 4]]
b = copy.copy(a)    
print("b = ", b)
print()

a[0] = 100
print("a[0] 에대한 변경 이후")
print("a = ", a)
print("b = ", b)
print()

a[1][0] = 200
print("a[1][0] 에 대한 변경 이후")
print("a = ", a)
print("b = ", b)

print()
print("이때 a의 주소값",id(a[1][0]))
print("이때 b의 주소값",id(b[1][0]))

a 값만 변경했는데, b[0][0] 100으로 변경된것을 볼 수있다.

list는 mutable한 타입이다. 여기서 파이썬은 mutable한 타입 안에 또다른 mutable한 타입이 있으면, 얕은 복사를 하더라도, 

두개의 mutable 타입은 같은 메모리 주소를 바라보는 특징이 있다. 따라서 중첩되는 mutable 타입을 활용할때 얕은 복사를 사용하게되면, 원치 않는 값들이 같이 변경될 문제점이 존재한다. 

 

 

요약하자면 

1. 복사된 객체의 바깥의 값을 변경할때는 복사객체와 원본이 그 값을 공유하지 않는다.

   따라서 b는 b[0]째 값을 변경하더라도 a에서는 값이 바뀌지 않는 것을 확인할 수 있다.

2. 복합객체(다른 객체를 포함하는 객체 ex) 리스트 속의 리스트)를 수정하려한다면 이야기가 달라진다.

    얕은복사에서는 복합객체를 수정하려고 한다면 원본의 객체와 복사한 객체 모두에게서 동시에 수정이 이루어진다.

따라서 a 얕은 복사한 객체 b에서의 내부리스트 수정은 원본인 a에게서도 동시에 발생한다.

 


2) 깊은 복사(Deep Copy)

  깊은 복사는 복사 대상의 값을 새롭게 만들어 준다. 즉 아예 독립적인 메모리 주소를 부여하기 때문에, 중첩되는 mutable 타입이 수정되더라도 원본과 상호간 영향을 미치지 않는다. 

위의 코드 예시를 deepcopy()를 통해서 확인해보자

import copy

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

a[0] = 100
print("a[0] 에대한 변경 이후")
print("a = ", a)
print("b = ", b)
print()

a[1][0] = 200
print("a[1][0] 에 대한 변경 이후")
print("a = ", a)
print("b = ", b)

print()
print("이때 a의 주소값",id(a[1][0]))
print("이때 b의 주소값",id(b[1][0]))

 


복사라는 키워드를 통해서 접근해봤지만, 사실 해당 게시글은 mutable, immutable한 상태일때 파이썬이 메모리주소를 어떻게 

핸들링하는지를 이해하는것이 더 중요한 포인트이다. 

mutable, immutable 객체에 대해서 각자 정리해보고, 특히 mutable한 객체에 대한 복사에 대해서 정리해보는 좋은 기회가 되었으면 좋겠다.

 

 

Index로 돌아가기