파이썬의 메모리 구조 및 메모리 할당 과정

최동혁·2022년 12월 6일
0

Python

목록 보기
6/10
post-thumbnail

파이썬의 메모리 구조 및 메모리 할당 과정

Everything is object in Python

x = 10
print(type(x))

>> <class 'int'>

C 언어

x = 10

이렇게 변수를 할당하면, 메모리에 해당 값이 바로 저장된다.

Python

x = 10

이렇게 변수를 할당하면, int라는 object를 만들어서 변수 x가 그 객체를 가리키는 형태이다.

x의 타입을 출력해보면 위와 같이 class가 나오는데, class를 구체화 한 것이 object이다.

Class와 Object

class는 추상적인 것이고, object는 구체적인 것이다.

Class

  • 파이썬에서 존재하는 타입은 class로 정의 된다. class의 예로는 list나 tuple 등이 있다.
  • class는 타입을 정의하는 것일 뿐이다.
  • 파이썬으로 우리가 list를 만들 때는, 사실 class를 사용해서 object를 만드는 것이다.
x = 10
y = x

if (id(x) == id(y)):
	print("x와 y는 같은 객체를 가리킨다")

>> x와 y는 같은 객체를 가리킨다
  • 그리고 y = x 라고 하면, x에 의해서 이미 만들어진 10이라는 int object를, y가 그냥 가르키기만 한다.
  • 그래서 x와 y는 10이라는, 같은 int object를 가리킨다.

x = 10
y = x

x = x + 1

if (id(x) != id(y)):
	print("x와 y는 다른 객체를 가리킨다")

>> x와 y는 다른 객체를 가리킨다
  • x = x + 1은 11 (10 + 1)이라는 새로운 int object를 생성한다.
  • 그리고 x는 새로 만들어진 int object를 가리킨다.
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 = 10은 10이라는 int object를 가리키는데, int object 10은 이미 x가 생성을 해놓고 나서 y가 받아서 가리키고 있기 때문에 만들 필요가 없고, 그냥 z가 10을 가리키기만 한다.

z = Car()

print(type(z))

>> <class'__main__.Car'>
  • z = Car()에서 Car라는 object가 생성되고 z는 Car object를 가리킨다.
  • 따라서 z의 type은 Car이다.

결론

  1. 변수를 할당
  2. 만약 해당 object가 없으면 생성
  3. 변수가 생성 혹은 존재하는 object를 가리킨다

immutable 객체와 mutable 객체에 대해서

immutable, mutable 객체 구분

파이썬에서는 객체의 종류를 두 가지로 구분할 수 있다.

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]이 모두 다른 주소인 것을 알 수 있다.

mutable, immutable 값이 변경될 때

immutable(상태 변경 불가능 - int, float, str…)

# 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(상태 변경 가능 - list, set, dictionary…)

# 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에서 볼 것은 두가지이다.

  1. d1, d2, d3의 값이 같다고 하더라도, 메모리에 각각 값 들이 생성되고 참조를 각각 하기 때문에 다들 주소가 다르게 나온다.
  2. d1, d2, d3에서 내부의 값이 변한다 하더라도, 최초 참조 메모리 주소가 변경되지 않는다.

결론

mutable 객체의 값은 메모리에 각각 할당이 되고

immutable 객체의 값은, 동일한 값에 참조가 여러 개 붙는다.

쉽게 말하자면

immutable은 주소를 할당한 상태에서 값을 바꾸게 되면 주소 또한 바뀌게 된다.

object를 생성 후 변수에 연결을 해주는데, 만약 다른 값으로 변경이 되면 변경된 object를 생성한 후 다시 변수에 연결을 해주는 식이다.

mutable은 주소를 할당한 상태에서 값을 바꾸게 되면 주소는 바뀌지 않고 안의 값들이 바뀐다.

profile
항상 성장하는 개발자 최동혁입니다.

0개의 댓글