3 minute read

1. 어느 상황에서 문제가 발생했나

다음과 같이 CSV를 파싱하는 과정에서 문제가 발생했었습니다.

# 리스트에 값 넣기
delimiter = ','
carriage_return = '\r'
linefeed = '\n'

index_str = str() # 원소 하나를 가지고 있는 문자열
row_list = list() # 한 행을 가지고 있는 리스트

for i in xy_str:
    if (i == delimiter):
        row_list.append(index_str)
        print(index_str)
        index_str = ""
        continue

    if (i == carriage_return):
        continue

    if (i == linefeed):
        row_list.append(index_str)
        index_str = "" 
        print(row_list)

        data_list.append(row_list) # 파이썬 append 연산은 얕은 복사이다.
        # row_list.clear() # 문제의 부분
        print(data_list)
        continue
    
    index_str += i
    
print(data_list)

row_list에 데이터를 쌓아서 data_list에 값을 추가해주는 코드인데, row_list.clear()를 하면 data_list에 추가해주었던 데이터에도 영향이 가서 data_list의 요소들이 다 사라지는 문제가 있었습니다..

2. immutable과 mutable

우선 파이썬에서 얕은 복사(shallow copy)와 깊은 복사(deep copy)의 차이를 이해하기 위해서는 파이썬의 mutable과 immutable을 이해해야 합니다.

2.1 immutable

C, C++에서 변수가 메모리에 잡히는 방식과는 다른 방식입니다. C, C++에서는 변수마다 각각 메모리를 할당받아서 가지고 있습니다. 그러나 python은 다른 방식을 채택하고 있습니다. 이를 한 번 살펴봅시다.

  • 우선 immutable은 수정 불가능한 객체를 뜻합니다. 그래서 값이 변하지 않습니다.
  • Immutable 객체의 타입은 대표적으로 int, float, str, tuple이 있습니다.
  • 파이썬에서는 immutable 객체의 값이 같은 경우에는 변수에 상관없이 동일한 곳을 참조합니다.

예시 코드는 다음과 같습니다.

variable1 = 99
variable2 = 99
variable3 = 99
variable4 = 99

print("variable1 주소 : %x" %(id(variable1)))
print("variable2 주소 : %x" %(id(variable2)))
print("variable3 주소 : %x" %(id(variable3)))
print("variable4 주소 : %x" %(id(variable4)))

immutable 변수 주소 출력

variable1 주소 : 7fb527518400
variable2 주소 : 7fb527518400
variable3 주소 : 7fb527518400
variable4 주소 : 7fb527518400

다음 그림을 한 번 살펴봅시다. image

스택에 있는 variable1,2,3,4들은 모두 7fb527518400의 주소값을 저장하고 있습니다. 이 주소를 참조하여 99라는 실제 값을 저장하고 있는 힙 메모리에 접근할 수 있습니다. 이 부분에서 알아야 99라는 값이 존재하는 메모리 주소를 모든 변수가 다 참조하고 있습니다. 이렇게 했을 때, 99라는 변수를 변수 개수만큼 메모리에 잡아놓을 필요없이 99라는 값이 하나만 존재해도 되기 때문에 메모리의 절약이 가능합니다.

변수 주소 변경

variable1 = 99
variable2 = 99
variable3 = 99
variable4 = 99

print("variable1 주소 : %x" %(id(variable1)))
print("variable2 주소 : %x" %(id(variable2)))
print("variable3 주소 : %x" %(id(variable3)))
print("variable4 주소 : %x" %(id(variable4)))

print("==================")

# 변수 주소 변경
variable2 = 12
variable3 = 12
print("변경된 variable2 주소 : %x" %(id(variable2)))
print("변경된 variable3 주소 : %x" %(id(variable3)))
variable1 주소 : 7fcffbed6400
variable2 주소 : 7fcffbed6400
variable3 주소 : 7fcffbed6400
variable4 주소 : 7fcffbed6400
==================
변경된 variable2 주소 : 7fcffbed5920
변경된 variable3 주소 : 7fcffbed5920

image 변수 값을 변경하면 위 이미지와 같은 그림이 만들어집니다.

2.2 mutable

  • mutable은 값이 변할 수 있는 객체입니다.
  • 변수마다 각각의 메모리가 따로 할당되어 있습니다.
# mutable 객체
arr1 = [1,2,3]
arr2 = [1,2,3]
arr3 = [1,2,3]
arr4 = [1,2,3]

print("arr1 주소 : %x" %(id(arr1)))
print("arr2 주소 : %x" %(id(arr2)))
print("arr3 주소 : %x" %(id(arr3)))
print("arr4 주소 : %x" %(id(arr4)))
arr1 주소 : 7fc77430fd00
arr2 주소 : 7fc774435e00
arr3 주소 : 7fc774439840
arr4 주소 : 7fc774435e80

image

주소 값이 다 다르게 할당되어 있는 것을 확인할 수 있습니다.

3. python Data Types and Variables

파이썬안의 모든 변수들은 객체로 간주됩니다.

4. 깊은 복사와 얕은 복사

python에서 깊은 복사를 사용하는 방법

import copy
x = [1,2]
y = x # 얕은 복사가 수행된다.

# 아래의 두가지 방법으로 깊은 복사를 사용할 수 있다.
z = x[:]
dcpy = copy.deepcopy(x)

print("array x의 주소값: %x" %id(x))
print("array y의 주소값: %x" %id(y))
print("array z의 주소값: %x" %id(z))
print("array dcpy의 주소값: %x" %id(dcpy))
array x의 주소값: 7fa440bdea80
array y의 주소값: 7fa440bdea80
array z의 주소값: 7fa440be8a00
array dcpy의 주소값: 7fa440b69d40

x와 y의 주소값이 같은 것을 확인할 수 있습니다. 저 상황에서는 얕은 복사가 수행된 것입니다.

4.1 append연산에서 깊은 복사를 하는 방법

원래 list의 append연산은 얕은 복사가 수행되지만, 다음과 같이 작성하면 깊은 복사를 수행할 수 있게 됩니다.

data_list.append(row_list[:])

리스트 변수 이름뒤에[:]을 붙여주면 깊은 복사의 수행이 가능합니다.

참고자료

python Data Types and Variables
python 메모리 모델
파이썬 mutable, immutable 설명
mutable과 immutable의 차이
Python shallow copy and deep copy in using append method

Categories:

Updated: