저번 글에서 객체와 객체지향 프로그래밍의 성격을 간단히 알아보았는데요,
파이썬 또한 모든걸 객체로 관리하기 때문에 객체지향을 지원하는 언어라고 할 수 있습니다.
print(isinstance('this is string', str))
print(isinstance(['this','is','list'], list))
print(isinstance(('this','is','tuple'),tuple))
# True
# True
# True
객체에는 세 가지 특징이 있습니다.
value | type | identity |
메모리에 저장된 값 | int, str, dict 등등 객체의 데이터 타입 일종의 객체 생성자 |
각 객체가 가진 고유한 ID 일종의 메모리 주소 |
type() 로 확인 가능 | id() 로 확인 가능 a is b 로 일치불일치 여부 확인 가능 |
그리고 그 객체들은 생성자(클래스)에 따라
크게 가변객체(mutable)와 불변객체(immutable)로 나뉩니다.
가변객체(mutable) |
불변객체(immutable) |
list, dict, set, 등등... |
str, int, float, tuple, 등등... |
불변객체는 이름 그대로 값을 바꿀 수 없는 객체입니다.
그렇다면 이게 무슨 뜻이냐?
string type으로 예시를 보겠습니다.
x = 'test1'
print(x)
x += ' test2'
print(x)
# test1
# test1 test2
'test1' value를 가진 x의 value를 'test1 test2'로 바꾼 결과입니다.
엥? 멀쩡히 잘만 바뀌는데? 라는 생각이 들지만,
내부적으로 들여다보면 사실 'test1'은 한번도 바뀐 적이 없습니다.
x = 'test1'
print(id(x))
x += 'test2'
print(id(x))
# 140450185151280
# 140450185152880
id()는 해당 값이 저장되어 있는 일종의 메모리 위치를 나타냅니다. <- (identity)
만약 'test1'의 value가 변경된 것이라면, 메모리 위치는 동일해야겠죠?
하지만 x에 새로운 value'test2'를 부여하면서 메모리 위치가 바뀐 걸 볼 수 있습니다. <- (identity 변경)
따라서 기존의 'test1'이 변경된 것이 아니라, 새로운 object를 생성한 것입니다.
(기존에 존재했던 value 'test1'은 메모리에서 제거됩니다.)
x = 1
print(id(x))
x += 1
print(id(x))
# 4318210752
# 4318210784
int type의 연산결과도 마찬가지입니다.
기존의 메모리 주소 속 value가 바뀌는 게 아니라 새로운 object를 생산하죠.
x = 'test1'
x[0] = 'tt'
# TypeError: 'str' object does not support item assignment
y = (1,2)
y[0] = 0
# TypeError: 'tuple' object does not support item assignment
따라서 불변객체는 이미 메모리 위에 있는 value를 변경할 수 없습니다.
x = 'test'
y = x
print(x is y)
# True
x = x + ' modified'
print(x is y)
# False
print(x)
print(y)
# test modified
# test
이로 미루어보아, 불변객체에서 위 두번째줄처럼 y = x가 실행되면
y는 x가 가리키는 메모리의 위치와 그 값을 직접적으로 가리킴을 알 수 있습니다.
가변객체는 불변객체와 비교해 어떤 식으로 차이가 나는지 보도록 하겠습니다.
대표적인 가변객체 list 예시를 들고와봤습니다.
x = [1, 2, 3]
print(id(x))
x += [4, 5]
print(id(x))
# 140325471720640
# 140325471720640
[1, 2, 3]의 value를 변경했는데, 메모리 위치는 변경되지 않았습니다.
이렇게 가변객체는, 이미 그 value가 메모리에 저장되었다 하더라도 그 내용을 수정할 수 있습니다.
x = [1, 2, 3]
print(id(x))
x[0] = 0
print(id(x))
y = {'a' : 1, 'b' : 2}
print(id(y))
y['a'] = 0
print(id(y))
print(x)
print(y)
# 140459840431680
# 140459840431680
# 140459840385792
# 140459840385792
# [0, 2, 3]
# {'a': 0, 'b': 2}
가변객체는 item assignment도 지원합니다.
x = [1, 2, 3]
y = x
print(x is y)
# True
x += [4, 5]
print(x is y)
# True
print(x)
print(y)
#[1, 2, 3, 4, 5]
#[1, 2, 3, 4, 5]
x에 [1, 2, 3] 이라는 object 값을 가리키도록 했습니다.
y에 x를 복사합니다.
헌데 x의 값을 변경했다고 y의 값도 변경되었습니다.
이로 미루어보아 가변객체에서의 copy는 메모리 주소와 그 값을 직접 point 하는 것이 아니라,
포인터만 복사함을 알 수 있습니다. (shallow copy)
코드를 짜다보면 이런 가변객체를 복사하여 작업하다가
참조 객체까지 변경되어 불편함을 겪을 수 있습니다.
이를 방지하기 위해서는 deepcopy 기능을 써야합니다.
from copy import deepcopy
x = [1, 2, 3]
y = deepcopy(x)
print(x)
print(y)
print(id(x))
print(id(y))
# [1, 2, 3]
# [1, 2, 3]
# 140532955555392
# 140532955557696
테스트 결과 x와 y의 주소값이 다름을 확인할 수 있습니다.
다시 코드를 봅시다.
x = (1,2)
y = (3,4)
print(id(x))
# 140511069579008
x += y
print(id(x))
# 140511069672288
불변객체를 통해 위와 같은 연산을 거치면
새로운 object가 생성되고, 새로운 메모리 공간을 할당해주어야 합니다.
그렇기 때문에 메모리 누수 발생 가능성이 있으며
매번 메모리의 빈 공간을 찾아야하는 이유 등 때문에 비싼 연산을 요구합니다.
따라서 프로그램이 커지고, 코드가 복잡해질 수록 성능을 저하시킬 수 있으니
어떤 객체를 선택해서 사용할지 신중히 고민해볼 필요가 있습니다.
하지만 불변객체는 그 값이 변하지 않는다는 점 때문에 신뢰도가 높습니다.
고로 상황에 따라 적절한 데이터를 골라 쓸 수 있도록 해야겠습니다.
참고
ichi.pro/ko/python-ui-byeongyeong-ganeung-mich-byeongyeong-bulganeunghan-gaegche-18971482174035
[python]함수의 인자 형태와 순서 / (non)default value parameter, *args, **kwargs (0) | 2021.03.17 |
---|---|
[Python] 문자열을 붙이는 다양한 방법 (string concatenation) (1) | 2021.03.17 |
파이썬 매직 메소드(던더 메소드) (2) | 2021.02.28 |
파이썬 예외처리 (try, except, else, finally, assert) (0) | 2021.02.26 |
객체지향 프로그래밍과 그 특징(추상화, 캡슐화, 상속성, 다형성) / 파이썬 예시 (2) | 2021.02.19 |
댓글 영역