TIL 08 | Mutable & Immutable Object

임종성·2021년 6월 16일
1

Python

목록 보기
1/7
post-thumbnail

파이썬은 객체(Object)라는 단위로 정보를 의미있는 덩어리로 묶고 관리한다. 그리고 객체는 Mutable(변경가능)하거나 Immutable(변경불가능)한 객체로 구분된다. 가변객체(Muatable Object)불변객체(Immutable Object)란 정확히 무엇일까? 이를 이해하기 위해 먼저 객체라는 개념을 알아보고 접근해보자.

객체(Object)

모든 프로그램은 정보가 어떻게 제공되는지와 관계없이 그저 정보를 메모리 위에 올린후 처리한다. 메모리는 단순히 비트(숫자)를 나열한 판이라고 생각할 수 있기 때문에, 메모리 위의 정보를 의미있는 덩어리로 묶기위한 단위가 필요하다. 파이썬에서는 이러한 정보, 데이터들을 객체(Object)라는 단위로 묶고 관리한다.

이러한 프로그래밍 기법을 객체지향프로그래밍(Object-oriented Programming)이라고 한다.

파이썬은 객체로 구성되어있고 객체지향 프로그래밍을 가능하게하는 언어이면서, 객체지향프로그래밍을 강제하지 않는 프로그래밍 언어이다.


객체의 세가지 특성

객체는 값(Value), 유형(Type), 정체성(Identity)라는 세가지 특성을 가진다.

  • Value : 객체를 통해 구할 수 있는 정보 그 자체
  • Type : 객체가 어떤 범주에 속하고 어떻게 다뤄야 하는지를 구별하기 위한 분류
  • Identity : 객체를 다른 객체와 구별하기 위한 고유번호이자, 메모리 상의 위치

Value와 Type이 동일한 데이터가 존재할 수는 있으나, 이들은 별개의 객체이고 각각 Identity가 다르다. 유형은 type() 함수로, 정체성은 id() 함수로 구할 수 있다.

Lim = 30 # 객체를 생성하고 변수 Lim에 대입
print(Lim) # 객체의 값  output: 30
print(type(Lim)) # 객체의 유형 output: <class 'int'>
print(id(Lim)) # 객체의 정체성 output: 140689226734800

** 변수 Lim은 객체가 아니라 객채에 붙인 이름이고 객체를 가리킬 뿐이다. 객체 하나에 여러 이름을 붙일 수 있다.


객체의 비교

파이썬에서는 객체를 비교하기 위해 id 함수와 is 연산자가 제공된다.

id(object)

id() 함수는 객체를 입력값으로 받아서 객체의 고유값(레퍼런스)을 반환하는 함수이다. id는 파이썬이 객체를 구별하기 위해서 부여하는 일련번호이고, 숫자로서의 의미는 없다. id() 함수는 동일한 객체인지 판별할 때 사용하며, 두 객체의 id() 함수 리턴값이 같다면 동일한 객체이다.

아이디 연산자(Identity Operator)

아이디 연산자 is는 객체 a, b의 id() 함수 리턴값이 같다면 True를 리턴한다.

Lim = 30           # 변수 Lim에 값 30을 가지는 객체 대입
Lim1 = Lim         # 변수 Lim1이 Lim이 가리키는 객체를 가리키도록 대입
print(Lim1)        # output : 30
print(id(Lim))     # output : 140309767003344
print(id(Lim1))    # output : 140309767003344
print(Lim is Lim1) # output : True // 같은 id를 가진다

Lim = Lim + 1      # 변수 Lim 변화
print(Lim1)        # output : 30 // 변수 Lim1이 가리키는 객체는 변하지않음
print(id(Lim))     # output : 140309767003376
print(id(Lim1))    # output : 140309767003344
print(Lim is Lim1) # output : False

이처럼 같은 id를 가지면 동일 객체로 인정하여 True를 리턴하고, 다르면 False를 리턴하여 동일 객체 여부를 판별한다.

** 데이터를 비교할 때에는 == 연산자를 사용하여 두 객체의 값이 동일한지 판별한다.

이 떄, 위와같이 Lim1이 Lim과 같은 객체를 가리키게 한 후 Lim의 값을 변화시키자 Lim의 id는 바뀌었지만 Lim1의 id는 그대로인 것을 확인할 수 있다. 값을 변화시켰을 경우 객체의 id도 그대로인 경우도 존재할까? 이를 구분하는 것이 바로 Mutable Object와 Immutable Object이다.

가변객체(Mutable Object)와 불변객체(Immutable Object)

객체지향 프로그래밍에 있어 불변객체란 생성 후 그 상태를 바꿀 수 없는 객체를 말한다. 반대로 생성 후 상태를 바꿀수 있는 객체를 가변객체라 한다. 상태를 바꾼다는 것은 무슨 의미일까? 다음은 가변객체와 불변객체의 예시이다.

Data Type
가변객체(Mutable Object)list, set, dict
불변객체(Immutable Object)int, float, bool, tuple, string, unicode

가변객체

가변객체는 객체를 생성한 후 객체의 값(Value)을 수정가능하고, 변수는 수정된 동일한 객체(identity가 동일한)를 가리키게 된다.

list를 이용한 예시

Lim = [92, 1, 11]   # 변수 Lim에 list type 객체 대입
print(id(Lim))      # Lim이 가리키는 객체의 id // output: 140237416484480
print(Lim)          # output: [92, 1, 11] 

Lim.append(1)       # 변수 Lim이 가리키는 객체의 값을 변경 
print(id(Lim))      # Lim이 가리키는 객체의 id // output: 140237416484480
print(Lim)          # output: [92, 1, 11, 1]

위와 같이 객체 [92, 1, 11]의 값을 변경했지만 객체의 id, 즉 데이터가 저장되는 메모리 위치는 변하지 않았음을 확인할 수 있다. 이렇듯 가변객체는 이미 Value가 메모리에 저장되었더라도 값을 변경할 수 있다.

그렇다면 가변객체의 copy는 어떤식으로 작용할까?

Lim = [92, 1, 11]      # 변수 Lim에 list type 객체 대입
Jongseong = Lim 
print(id(Lim))         # Lim이 가리키는 객체의 id // output: 140610175848192
print(id(Jongseong))   # Jongseong이 가리키는 객체의 id // output: 140610175848192
print(Lim)             # output: [92, 1, 11] 
print(Jongseong)       # output: [92, 1, 11] 

Lim.append(1)          # 변수 Lim이 가리키는 객체의 값을 변경 
print(id(Lim))         # Lim이 가리키는 객체의 id // output: 140610175848192
print(id(Jongseong))   # Jongseong이 가리키는 객체의 id // output: 140610175848192
print(Lim)             # output: [92, 1, 11, 1]
print(Jongseong)       # output: [92, 1, 11, 1]

Jongseong에 Lim을 대입해 복사하고 Lim의 값을 변경했더니 Jongseong의 값도 변경된 것을 확인할 수 있고, Jongseong이 메모리 주소와 값을 직접 포인팅하는 것이 아니라 그저 포인터만 복사함을 알 수 있다.


불변객체

불변객체는 객체를 생성한 후 객체의 값을 수정 불가능하고, 변수는 해당 값을 가진 다른 객체를 가리키게된다. 아이디 연산자를 설명할 때의 코드를 다시 한번 살펴보자.

int를 이용한 예시

Lim = 30           # 변수 Lim에 값 30을 가지는 객체 대입
Lim1 = Lim         # 변수 Lim1이 Lim이 가리키는 객체를 가리키도록 대입
print(Lim1)        # output : 30
print(id(Lim))     # output : 140309767003344
print(id(Lim1))    # output : 140309767003344
print(Lim is Lim1) # output : True // 같은 id를 가진다

Lim = Lim + 1      # 변수 Lim 변화
print(Lim)         # output : 31
print(Lim1)        # output : 30 // 변수 Lim1이 가리키는 객체는 변하지않음
print(id(Lim))     # output : 140309767003376
print(id(Lim1))    # output : 140309767003344
print(Lim is Lim1) # output : False // 다른 id를 가진다. 새로운 객체 생성

Lim이 가리키는 객체는 30의 값을 가지고 있었고, Lim = Lim + 1을 이용해 값을 바꾸고 출력했더니 31로 잘 바뀐것처럼 보인다. 그런데 왜 불변객체라는 것일까?

엄밀히 따지면 30의 값을 가진 객체는 변하지 않았다. 동일한 id를 가지면서 값이 바뀌어야 변했다고 할 수 있지만, 실제로 Lim의 값은 바뀌면서 다른 id, 즉 다른 메모리 위치에 새로운 객체를 생성했음을 알 수 있다.

이렇게 Lim이 새로운 객체를 생성했지만 Lim의 copy인 Lim1은 변경 전의 값과 메모리 위치 그대로 가지고 있다. 따라서 불변객체의 copy는 메모리 위치와 그 값을 직접 가리키는 것을 알 수 있다.

** 가변객체의 copy를 얕은 복사(shallow copy)라 한다. 얕은복사와 깊은 복사(deep copy)는 어떤 개념일까? 또한 함수의 호출 방식에 Call by Reference와 Call by Value가 있다고 한다.이 호출 방식과 가변객체, 불변객체는 어떤 관계가 있을까?

profile
어디를 가든 마음을 다해 가자

0개의 댓글