가변 객체(Mutable Object) vs 불가변 객체(Immutable Object)
가변객체 : 객체의 값을 변경할 수 있는 객체
불가변객체 : 객체의 값을 변경할 수 없는 객체
Python에서는 자료형 타입에 따라 가변 객체와 불가변 객체가 다음과 같이 나뉜다.
가변객체 | 불가변객체 |
리스트(list) | 정수(int) |
딕셔너리(dict) | 실수(float) |
집합(set) | 문자열(string) |
논리형(bool) |
가변 객체(mutable object)의 경우, 객체의 값이 변경 가능하므로 얕은 복사(shallow copy)가 이루어지고,
불가변 객체(immutable object)의 경우, 객체의 값이 변경될 수 없으므로 깊은 복사(deep copy)가 이루어진다.
li = [1,2,3,4]
print(id(li))
li[0] = 0
print(id(li))
>> 1578716970816
>> 1578716970816
위 코드에서 list 자료형인 li는 가변 객체이므로 값을 변경해도 같은 위치 정보값을 담고 있다.
def foo(li):
li[0] = 6
pass
li = [3]
print(li)
>> 6
그래서 리스트를 함수의 인자로 전달하고 함수 안에서 배열에 변경을 가하면 함수가 종료된 후에도 변경사항이 유지된다.
num = 0
print(id(num))
num = 1
print(id(num))
>> 1578715578576
>> 1578715578608
위 코드에서 정수 자료형인 num은 불가변 객체이므로 값을 변경하니 다른 위치 정보값을 담고 있다.
def bar(x):
print("2:", x, id(x))
x = 0
print("3:", x, id(x))
pass
n = 100
print("1:", n, id(n))
bar(n)
print(n) # n is not 0!
>> 1: 100 2264641572176
>> 2: 100 2264641572176
>> 3: 0 2264641568976
>> 100
그래서 정수 변수를 함수 인자로 전달하고 함수에서 값의 변경을 가해도 함수 종료 후 원래 변수의 값은 그대로다.
깊은 복사(Deep Copy)
깊은 복사란, 복사의 대상 자체가 통채로 복사되는 복사를 말하며, 복사 대상과 복사된 대상은 완전히 독립적인 메모리를 차지한다. 데이터 타입이 value type일 경우 깊은 복사가 일어난다.
num = 888
num2 = num #shallow copy
num = 777 #(deep) copy on write
print(num)
print(num2)
>> 888
>> 777
num의 값을 바꿔도 num2는 기존 888을 유지하고 있다.
num2로 num의 깊은 복사가 이루어진 것이다.
이는 num의 데이터 타입이 value type이기 때문이다.
변수의 데이터타입을 보려면 type() 명령어를 사용하면 된다.
print(type(num))
>> <class 'int'>
* Copy on Write
위 코드에 대해 자세히 설명하겠다.
엄밀히 말하면, 코드 'num2 = num'에서 deep copy가 이루어진 것은 아니다.
이를 설명하려면 copy on write에 대해 설명해야 한다.
Deep copy는 성능 overhead가 굉장한데, copy on write은 불필요한 deep copy를 막기 위해 사용되는 기술이다.
num2 = num에서 일단 shallow copy를 하고 이후 num이나 num2에 변화가 생기면 그때 deep copy를 진행한다.
num과 num2가 어차피 같은 값을 갖는다면 굳이 성능 감소를 감수해가면서 deep copy를 할 필요가 없다는 것이 취지다.
num = 777
num2 = num
if (num2 is num): #id 비교
print("shallow copy")
else:
print("deep copy")
>> shallow copy
얕은 복사(Shallow Copy)
얕은 복사란, 복사의 대상 자체가 통채로 복사되는 것이 아니라 주소값만을 복사하는 복사를 말한다.
새로운 인스턴스를 생성하지 않기에 복사 대상과 복사된 대상이 같은 메모리를 가르키고 있다.
깊은 복사보다 속도가 더 빠르고 메모리 사용량이 적다.
class MyClass:
num = 0
def __init__(self, x):
self.num = x
obj = MyClass(3)
obj2 = obj
obj3 = obj
print(id(obj))
print(id(obj2))
print(id(obj3))
>> 2222408813584
>> 2222408813584
>> 2222408813584
obj, obj2, obj3 3개의 객체가 모두 같은 위치 정보를 담고 있는 것을 볼 수 있다.
Object Interning
Object Interning이란, 불가변 객체(immutable object)에 대해 하나의 동일한 값을 가진 객체만을 메모리에 저장하여 메모리를 효율적으로 관리하는 방식이다.
Object Interning이 일어나는 대상은 2가지다.
- 문자열: 20자 미만의 공백을 포함하지 않은 문자열
- 정수: -5 ~ 256
num1 = 6
num2 = 5
num2 += 1
print(id(num1))
print(id(num2))
>> 1675551441136
>> 1675551441136
위 코드에서 num1과 num2는 초기값 6과 5를 가진다.
메모리에서는 다음과 같은 구조일 것이다.
num2에 1이 더해져 6이 된다. 6을 담은 객체를 num1과 num2를 위해 따로따로 만들면 비효율적이니, 6을 담은 객체는 하나만 생성하고 num1과 num2가 이를 공유한다. 이것이 바로 Object Interning이다.
'Python' 카테고리의 다른 글
Python 함수 선언 방법, 변수 지역성 (0) | 2024.03.27 |
---|---|
Python 이터러블(iterable)과 반복자(iterator) (0) | 2024.03.27 |
Python 컬렉션 Comprehension (0) | 2024.03.27 |
Python 라이프 사이클과 reference count (0) | 2024.03.27 |
Python 변수와 메모리 (0) | 2024.03.27 |