📌 mutable vs immutable
파이썬의 모든것은 객체로 이루어져있다.
흔히들 외우고 있어서 파이썬엔 mutable(변경가능) 객체와 immutable(변경불가능) 객체가 있다고 알고있을 것이다.
프로그램 실행 시 object의 type이 정해지는데 이후에 변경이 가능하면 mutable object, 변경이 불가능하면 immutable object 이다.
immutable | int, float, bool, str, tuple |
mutable | list, set, dictionary |
📌 객체 중심 파이썬
객체(object) 중심언어이다.
객체에는 세가지 속성이 있다.
- identity(id) – 컴퓨터 메모리에서 객체가 참조하는 주소
- type – 생성되는 개체의 종류를 나타냅니다. ex) list, string, int
- value – 객체가 저장하는 값 ex) list = [1, 2, 3] 에서 1, 2, 3
id와 type은 변경 할 수 없지만 mutable한 객체의 value는 변경 할 수 있다.
객체, 객체를 참조하는 변수, 참조의 변경 세가지를 중점적으로 보자.
C언어
int a; // int형 변수 선언
a = 1; // 1이라는 정수 값 할당
a = 2; // 2라는 정수 값 재 할당
C언어의 경우 변수 중심 언어로 위 코드와 같이 값 할당이 이루어진다. 그럼 이제 객체 중심 이라는 파이썬에 대해서 보자.
Python
a = 1
# 1. 1이라는 객체 생성
# 2. 참조변수 a가 1이라는 객체를 참조한다.
a = 2 # 2. 참조변수 a가 2라는 객체를 다시 참조한다.
위의 파이썬 예제에서 객체는 1, 2 / 객체를 참조한는 변수는 a / 참조의 변경은 = 으로 이루어진다.
이미지로 보면 아래와 같다.
📌 Immutable object
a = 1 # 객체 생성과 변수 a의 참조
b = a # 변수 b가 a가 참조하는 값(1)을 참조
print(id(a))
print(id(b))
print(id(1))
a = 2 # 2라는 객체 생성 후 a는 2를 참조
print(id(a))
print(id(b))
print(id(1))
print(id(2))
a = 1, b = a가 실행 된 이후 a ,b 는 모두 1 객체를 가리키고 있기 때문에
a, b, 1의 id값은 모두 동일하다.
a = 2 이후 a가 참조하는 객체가 2로 변경되었으며 b는 변화가 없으므로
a와 2의 id값이 동일하며 b와1의 id값이 동일하게 출력된다.
이것도 이미지로 보면 아래와 같다.
muttable, immutable을 생각하며 다시보자.
a라는 변수에 참조된 객체는 1에서 2로 변경되었다.
하지만 객체 1은 절대 바뀌지 않는다. int type의 immutable 객체 이기 때문이다.
a = 2 를 할당했을 때 immuitable한 1의 값을 변경하려 했기 때문에 새로운 메모리 공간에 2라는 값을 가진 객체를 만들고 a에 새롭게 연결시킨 것이다.
📚 immutable object 변경하기
immutable객체가 변경 불가능하다면 문자열의 replace같은 변경은 뭐지? 라고 생각 할 수 있다.
아래 예제를 보자.
a = (1, 2, 3)
print(id(a))
a += (4, )
print(id(a))
a라는 튜플 객체에 4를 추가해주고 있다. 위 코드는 정상적으로 동작한다.
왜 이것이 가능할까? 튜플은 변경이 불가능하다고 하지 않았나?
자세히 코드를 보자. a = a + (4, ) 이다.
a가 가리키고 있는 튜플을 수정하는게 아닌 기존의 a를 복사해서 새로운 튜플을 만들어서 a에 다시 할당해주고있다.
즉 첫번째 print문의 id(a) 와 두번째 print문의 id(a)는 각각 다른 값을 가진다.
문자열도 마찬가지로 동작한다.
text = 'abcde'
print(id(text))
text = text.replace('a', 'x')
print(text) # xbcde
print(id(text))
📌 Mutable Object
a = ["해선", 1]
# 1. 리스트 객체 생성
# 2. 리스트의 요소가 "해선", 1 참조
print(id(a))
b = a # a가 참조하는 객체를 b가 참조
print(id(b))
a.append(2) # a가 참조하는 객체에 2 추가
a[0]="해섬" # a가 참조하는 객체의 0번째 요소를 변경
print(id(a))
print(a, b) # a : ["해섬", 1, 2] b : ["해섬", 1, 2]
list type의 object a를 만들었다. 이 list 는 2개의 immutable 한 int object들을 참조하고있다.
b = a 에서 b는 a가 참조하고있는 객체를 참조한다.
따라서 a를 사용해서만 값을 변경했지만 b도 변경된 값이 출력된다.
또한 id(a), id(b)는 값을 변경하기 전과 후 모두 같은 id값을 가진다.
append로 값을 추가하고 인덱스로 a[0]의 값을 변경했지만 여전히 같은 리스트를 참조하고 있는 것이다.
이것이 바로 mutable object이다.
그림으로 보면 아래와 같다.
📌 Immutable의 예외
사실 이것 때문에 이 글을 쓰기 시작했다.
다음 코드의 결과는 어떻게 될까?
a는 튜플이고 우리는 튜플이 변경 불가능한 immutable 자료형 이라는 것을 알고있다.
a = (1, 2, ["해선",1])
a[2].append(2)
라고 생각해서 a는 튜플이기 때문에 변경 불가능해서 오류가 발생한다! 라고 답하고싶지만 틀렸다.
모든 immutable한 객체가 immutable한 것은 아니다.
일반적으로 튜플은 변경 불가능하다. 아래 코드와 같이 값을 변경하려하면 오류가 난다.
a = (1, 2, 3)
a[2] = 30
그림으로 살펴보자.
정확히 말하면 immutable은 tuple객체에서 다른 객체로의 참조의 변경이 불가능한 것이다.
즉 a[2] = 30 이라는 것은 a[2]가 3을 참조하고 있던 것을 30이 참조하도록 만들겠다 라는 것 인데 튜플은 immutable이기 때문에 이 참조를 변경할 수 없어 오류가 발생한다.
그럼 다시 처음 말한 코드를 살펴보자.
a = (1, 2, ["해선",1])
a[2].append(2)
a는 튜플이고 immutable하지만 a[2]의 원소는 mutable한 list object 이다.
그림으로 보면 아래와 같다.
a[2]. append(2)라는 것은 a[2]가 가리키고 있는 리스트 객체에 2를 추가하겠다 라는 말이다. 객체의 참조를 변경하겠다는 의미가 아니기때문에 (노란색 연결관계를 변경하겠다는 것이 아니기 때문에
list는 mutable한 객체이므로 위 그림과 같이 정상적으로 2라는 값을 추가할 수 있다.
tuple이 immutable하다는 것은 위 그림의 노랑색 연결관계를 변경 할 수 없다는것을 의미한다.
즉, 내가 만약 a[2].append(2)가 아닌 a[2] = ["해선", 1, 2] 로 변경을 시도했다면 마찬가지로 TypeError가 발생했을 것이다.
📌 정리
- mutable은 만든 후 수정이 가능하고 immutable은 만든 후 수정이 불가능하다.
- python은 각각 mutable 과 immutable 한 object 들을 다르게 다룬다.
- Mutable object는 크기나 값에 대한 변화가 필요한 object 를 만들때 주로 사용한다.
- Immutable object는 항상 같은 값을 확정하고 싶은 object들을 필요로 할때 주로 사용한다.
- Immutable object를 변경하기 위해서는 복사본을 만드는 작업이 필요하다.
- immutable object에 예외가 존재한다.
다음편에서는 이어서 이 object들이 함수로 전달 될 때 어떻게 동작하는지에 대해 알아보겠다.
📎 참조
https://www.mygreatlearning.com/blog/understanding-mutable-and-immutable-in-python/