티스토리 뷰

반응형

파이썬에는 다른 언어에서는 찾아볼 수 없는 특징들이 몇 가지 있는데, 대표적으로 파이썬은 모든 객체를 가변 객체(mutable object), 불변 객체(immutable object) 두 종류의 객체로 구분하고 있다.

 

어렵지 않은 개념이지만, 중요한 개념이므로 이번 포스팅에서 간략하게 정리하고 간다.

파이썬에서의 mutable, immutable에 대한 개념을 알아보자.

 


1. 가변(mutable) vs 불변(immutable)

먼저, 가변 객체와 불변 객체에 대한 이해를 하여보자.

 

불변적 객체(immutable)란, 메모리 안에 담겨 있는 값이 언제나 변하지 않는 객체를 의미한다. 예를 들면, 다음과 같은 종류들은 불변 객체이다.

  • 정수형(int) & 불리언형(boolean)
  • 실수형(float)
  • 복소수형(complex)
  • 문자열형(string)
  • 튜플형(tuple)
  • 바이트형(bytes)
  • frozenset형
  • decimal 라이브러리의 Decimal형
  • range형

반대로, 가변적 객체(mutable)란 메모리 안에 담겨 있는 값이 변할 수 있는 객체를 의미한다. 다음고 ㅏ같은 같은 종류들은 가변 객체이다.

  • 리스트형(list)
  • 집합형(set)
  • 사전형(dictionary)
  • 바이트 배열형(bytearray)
  • 사용자 정의 class형 (이는 사용자가 불변 객체로 정할 수 있지만, 일반적인 경우 가변 객체이다.)

이에 대하여 간단히 탐구하여보자. 다음과 같이 불변 객체의 한 종류인 정수형에 덧셈을 한 경우 메모리 주소가 바뀌는지 안 바뀌는지 알아보자.

 

1
2
3
4
5
6
7
8
9
10
11
= 1
before_plus = id(a)
+= 1
after_plus = id(a)
 
print(before_plus, after_plus)
# 1887852167408 1887852167440
print(before_plus == after_plus)
# False
 
# 1을 더했을 때 메모리 주소가 바뀐 모습을 볼 수 있다.
cs

 

before_plus에는 a에 1을 더하기 전 메모리 주소를 담았고, after_plus에는 a에 1을 더한 후 메모리 주소를 담았다. 그 결과 메모리 주소가 달라진 것을 확인할 수 있다.

 

파이썬 사용자들이 가지는 대표적인 오개념 중 하나이다. a가 가리키는 메모리 안의 값이 1에서 2로 바뀐 것이 아닌, 정수 1을 담고 있는 객체를 가리키는 주소에서 정수 2를 담고 있는 객체로 a의 주소가 바뀐 것이다. 다른 말로, '=' 연산자는 할당 연산자의 개념에 더 가깝지, 데이터를 변화시키는 개념이 아니다.

 

이유는 간단하다. 정수형(int)은 불변 객체이기 때문이다. 메모리에 담긴 데이터(1)는 무슨 일이 있어도 바뀌지 않기 때문에, '정수 2'라는 값을 나타내 주기 위해선 새로운 공간에 데이터(2)를 할당해주어야 하기 때문이다.

 

따라서 새로운 메모리 주소를 a와 연결 지었고, 따라서 메모리 주소를 반환하는 id 함수의 반환 값이 달라지는 것이다.

로딩중입니다.
a의 값이 1에서 2 바뀌는 것이 아닌, 객체의 메모리 주소가 바뀌는 것이다.

 

따라서, 직접적으로 불변 객체의 데이터를 바꾸는 것은 불가능하다. 다음과 같이 불변형의 한 종류인 문자열형 객체 안의 데이터를 바꾸어보자.

 

1
2
3
= "abcde"
a[0= 'F'
print(a)
cs

 

이 경우 다음과 같은 에러 메시지를 확인할 수 있다.

Traceback (most recent call last):
  File "immutable.py", line 2, in <module>
    a[0] = 'F'
TypeError: 'str' object does not support item assignment

에러 메시지를 살펴보면, 문자열형(str)은 할당이 불가능하다고 한다. 메모리 안의 데이터를 바꾸지 못하기 때문이다. 이는 다른 불변 객체 모두 해당되는 이야기이다.


이번에는 가변 객체인 list에 대하여 비슷한 연산을 하여보자. 다음과 같이 list에 새로운 원소(5)를 추가하여 이에 대한 메모리 주소가 바뀌는지 알아보자.

 

1
2
3
4
5
6
7
8
9
10
11
= [1234]
before_plus = id(a)
a.append(5)
after_plus = id(a)
 
print(before_plus, after_plus)
# 1722206761856 1722206761856
print(before_plus == after_plus)
# True
 
# 원소를 추가해도 메모리 주소가 바뀌지 않는다.
cs

 

이번에는 정수형의 덧셈과 다르게, before_plus와 after_plus가 같은 메모리 주소를 가짐을 알 수 있다.

 

이유는 똑같이 간단하다. list는 가변 객체이기 때문에, a라는 list에 할당되어 있는 메모리 주소에 담긴 데이터가 변한 것이다.

로딩중입니다.
a가 연결된 메모리 주소에 담긴 데이터가 바뀐 것이다.

혹은 다음과 같이 기존의 객체의 데이터를 수정할 수 있다.

 

1
2
3
4
5
6
7
8
= [1234]
a[0= 5
print(a)
# [5, 2, 3, 4]
 
del a[3]
print(a)
# [5, 2, 3]
cs

 

이처럼 가변 객체는 데이터의 추가, 삭제, 수정에 자유롭다.


그럼, 다음과 같이 불변 객체 안의 가변 객체는 수정이 자유로울까? 다음 코드와 같이 불변 객체(tuple) 안의 가변 객체(list)를 수정해보았다.

 

1
2
3
4
= (123, ['a''b''c'])
a[3].append('d')
print(a)
# (1, 2, 3, ['a', 'b', 'c', 'd'])
cs

 

이 경우 수정이 되는 모습을 살펴볼 수 있다. 즉, 불변 객체안의 가변 객체는 수정이 되는 것을 확인할 수 있다.

 

이번엔 반대로 가변 객체 안의 불변 객체를 수정하여보자. 다음과 같이 가변 객체(list)안의 불변 객체(string)을 수정하여보자.

 

1
2
3
4
5
6
7
8
= [123"456"]
a[3][1= "7"
print(a)
 
# Traceback (most recent call last):
#   File "mutable.py", line 2, in <module>
#     a[3][1] = "7"
# TypeError: 'str' object does not support item assignment
cs

 

이번에는 수정이 되지 않는 모습을 확인할 수 있다. 따라서, 객체의 수정 여부는 상위 객체에 상관없이 그 객체의 수정 여부에 따라 달려있다는 것을 알 수 있다.


다른 불변, 가변 객체도 비슷한 원리로 할당된다. 만약 불변 객체에 다른 값을 담아도 id가 같은 값이 출력된다면, 이는 불변 객체가 변해서가 아닌, 파이썬에서 자동으로 메모리 정리(이를 우리는 파이썬의 GC, Garbage Collection라 한다.)를 하면서 우연히 같은 메모리 주소에 다른 값이 들어갔기 때문이다.

 

가변 객체와 불변 객체라는 개념은 간단하지만, 파이썬에서는 중요한 개념이므로 숙지하는 것을 추천한다.

 

 

 

 

 

 

 

반응형
댓글
Total
Today
Yesterday
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/05   »
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
글 보관함