x = 10
print(type(x))
>> <class 'int'>
C 언어
x = 10
이렇게 변수를 할당하면, 메모리에 해당 값이 바로 저장된다.
Python
x = 10
이렇게 변수를 할당하면, int라는 object를 만들어서 변수 x가 그 객체를 가리키는 형태이다.
x의 타입을 출력해보면 위와 같이 class가 나오는데, class를 구체화 한 것이 object이다.
class는 추상적인 것이고, object는 구체적인 것이다.
Class
x = 10
y = x
if (id(x) == id(y)):
print("x와 y는 같은 객체를 가리킨다")
>> x와 y는 같은 객체를 가리킨다
x = 10
y = x
x = x + 1
if (id(x) != id(y)):
print("x와 y는 다른 객체를 가리킨다")
>> x와 y는 다른 객체를 가리킨다
x = 10
y = x
x = x + 1
z = 10
if (id(y) == id(z)):
print("y와 z는 같은 memory를 가리킨다")
else:
print("y와 z는 다른 object를 가리킨다")
>> y와 z는 같은 memory를 가리킨다
z = Car()
print(type(z))
>> <class'__main__.Car'>
파이썬에서는 객체의 종류를 두 가지로 구분할 수 있다.
mutable - 변경되는 객체 (객체의 상태를 변경할 수 있다)
immutable - 변경되지 않는 객체 (객체의 상태를 변경할 수 없다)
변경이 되는 mutable 객체의 종류는 list, set, dictionary 정도가 있고,
변경이 되지 않는 immutable 객체의 종류는 int, float, tuple, str, bool이 있다.
얕은 복사, 깊은 복사를 이해하기 위해서는 필요하다.
파이썬에서는 immutable 객체의 값이 같은 경우에 변수에 상관없이 동일한 곳을 참조한다.
mutable 객체의 경우에는 모든 객체를 각각 생성해서 참조해 준다.
# immutable 객체 (상태 변경 X)
print("immutable 객체")
a = 99
b = 99
c = 99
d = 99
e = 99
print(hex(id(a)))
print(hex(id(b)))
print(hex(id(c)))
print(hex(id(d)))
print(hex(id(e)))
# mutable 객체 (상태 변경 O)
print("\nmutable 객체")
arr1 = [1, 2, 3]
arr2 = [1, 2, 3]
arr3 = [1, 2, 3]
arr4 = [1, 2, 3]
print(hex(id(arr1)))
print(hex(id(arr2)))
print(hex(id(arr3)))
print(hex(id(arr4)))
immutable
변수 a, b, c, d, e에는 각각 99라는 값이 있다.
보통 c, c++에서는 각 변수마다 메모리를 주고 그 주소 값이 달라야 하는데 파이썬에서는 다르다.
하나의 immutable 값에 여러 개의 참조가 붙게 된다.
그래서 a, b, c, d, e의 주소를 보면 전부다 같은 곳을 가리킨다.
immutable 객체들은 값이 바뀌면 참조만 바꾸면 되기 때문에 이런 식으로 설계를 했다.
mutable
리스트 arr1, arr2, arr3, arr4도 모두 같은 값을 집어넣어서 생성했다.
리스트는 mutable 객체이다.
주소의 결과를 보면 arr1, 2, 3, 4가 참조하는 [1, 2, 3]이 모두 다른 주소인 것을 알 수 있다.
# immutable 객체
print("=" * 50)
print("immutable 객체 예제.")
print("=" * 50)
print("1. int 값이 변경되면?")
num1 = 99
num2 = 99
num3 = 99
num4 = 99
print(f"num1 값 : {num1} \t주소 : {hex(id(num1))}")
print(f"num2 값 : {num2} \t주소 : {hex(id(num2))}")
print(f"num3 값 : {num2} \t주소 : {hex(id(num3))}")
print(f"num4 값 : {num4} \t주소 : {hex(id(num4))}")
num1 += 1 # num1 값 증가
num3 += 1 # num3 값 증가
num4 += 10 # num4 값 증가
print()
print("값 변경 후 주소 값 확인")
print(f"num1 값 : {num1} \t주소 : {hex(id(num1))}")
print(f"num2 값 : {num2} \t주소 : {hex(id(num2))}")
print(f"num3 값 : {num3} \t주소 : {hex(id(num3))}")
print(f"num4 값 : {num4} \t주소 : {hex(id(num4))}")
print("\n2. str 값이 변경되면?")
s1 = "BlockDMask"
s2 = "BlockDMask"
s3 = "BlockDMask"
s4 = "BlockDMask"
print(f"s1 값 : {s1} \t주소 : {hex(id(s1))}")
print(f"s2 값 : {s2} \t주소 : {hex(id(s2))}")
print(f"s3 값 : {s3} \t주소 : {hex(id(s3))}")
print(f"s4 값 : {s4} \t주소 : {hex(id(s4))}")
print()
print("값 변경 후 주소 값 확인")
s1 = s1.replace('D', 'ZZZ') # replace 로 값을 변경하고, 새로운 문자열을 s1에 대입하게됨.
s2 = "BlockZZZMask" # replace 로 변경한 문자열과 동일한 문자열로 변경함
s4 = s3.upper() # s4를 대문자로 변경
print(f"s1 값 : {s1} \t주소 : {hex(id(s1))}")
print(f"s2 값 : {s2} \t주소 : {hex(id(s2))}")
print(f"s3 값 : {s3} \t주소 : {hex(id(s3))}")
print(f"s4 값 : {s4} \t주소 : {hex(id(s4))}")
int 타입
99일 때 주소가 뒤에 네 자리만 보면 0d50으로 같았는데, 100으로 변경되니 05d0로 변경된 것을 볼 수 있다.
또한, 100으로 변경한 것이 num1, num3인데 둘 다 또 같은 100을 참조하는 것을 볼 수 있다.
109로 변경한 num4도 당연히 참조가 바뀌었다.
str 타입
최초 s1 ~ s4가 동일한 “BlockDMask” 라는 문자열을 참조하고 있는 것을 알 수 있다.
따로 문자열을 생성했어도 불구하고 동일한 메모리를 참조하는 것이 immutable 객체의 특징이다.
그러나 항상 값이 동일하다고 같은 곳을 가리키는 것은 아니다.
int 타입의 경우에는 99가 100으로 변경되어도 다른 변수들이 같은 100을 참조한다.
str 타입의 경우에는 s1, s2가 항상 문자열이 같은지 보고 같은 곳을 참조할지 판단하기가 쉽지 않기 때문에 값이 같다고 항상 같은 곳을 참조 하지는 않는다.
중요한 것은 immutable 객체는 거의 대부분 같은 값을 참조한다.
# mutable 객체
print("=" * 50)
print("mutable 객체 예제.")
print("=" * 50)
print("1. list 값이 변경되면?")
arr1 = ['a', 'b', 77]
arr2 = ['a', 'b', 77]
arr3 = ['a', 'b', 77]
print(f"arr1 값 : {arr1} \t주소 : {hex(id(arr1))}")
print(f"arr2 값 : {arr2} \t주소 : {hex(id(arr2))}")
print(f"arr3 값 : {arr3} \t주소 : {hex(id(arr3))}")
arr1.append(10) # ['a', 'b', 77, 10]
arr2.append(10) # ['a', 'b', 77, 10]
print(f"arr1 값 : {arr1} \t주소 : {hex(id(arr1))}")
print(f"arr2 값 : {arr2} \t주소 : {hex(id(arr2))}")
print(f"arr3 값 : {arr3} \t주소 : {hex(id(arr3))}")
print("\n2. dictionary 값이 변경되면?")
d1 = {'a': 11, 'b': 22, 'c': 33}
d2 = {'a': 11, 'b': 22, 'c': 33}
d3 = {'a': 11, 'b': 22, 'c': 33}
print(f"d1 값 : {d1} \t주소 : {hex(id(d1))}")
print(f"d2 값 : {d2} \t주소 : {hex(id(d2))}")
print(f"d3 값 : {d3} \t주소 : {hex(id(d3))}")
d1['a'] = 99
d2['d'] = 44
print(f"d1 값 : {d1} \t주소 : {hex(id(d1))}")
print(f"d2 값 : {d2} \t주소 : {hex(id(d2))}")
print(f"d3 값 : {d3} \t주소 : {hex(id(d3))}")
mutable에서 볼 것은 두가지이다.
mutable 객체의 값은 메모리에 각각 할당이 되고
immutable 객체의 값은, 동일한 값에 참조가 여러 개 붙는다.
쉽게 말하자면
immutable은 주소를 할당한 상태에서 값을 바꾸게 되면 주소 또한 바뀌게 된다.
object를 생성 후 변수에 연결을 해주는데, 만약 다른 값으로 변경이 되면 변경된 object를 생성한 후 다시 변수에 연결을 해주는 식이다.
mutable은 주소를 할당한 상태에서 값을 바꾸게 되면 주소는 바뀌지 않고 안의 값들이 바뀐다.