Python의 변수와 참조

변수는 일반적으로 데이터를 저장할 수 있는 메모리 공간을 의미합니다.

다만, 파이썬에서의 변수는 C, 자바 같은 컴파일 언어에서의 변수와는 차이가 있습니다.

자바의 변수

자바를 예로 들면 자바는 변수를 선언하면서 선언된 자료형 만큼의 메모리를 확보합니다. 더 정확하게 말하면 원시 타입(Primitive Type)에 해당하는 변수(int, double, boolean 등)를 선언하면 선언된 타입만큼의 크기의 영역을 Stack 메모리에 선언합니다. 그리고 객체와 메서드 같은 참조형 타입(Reference type)은 Heap 메모리에 실질적인 데이터가 저장되고 그 데이터의 주소값이 stack 메모리에 저장되는 형태입니다.

파이썬의 변수

하지만 파이썬의 변수는 다릅니다.

파이썬에서 변수는 타입에 의해 선언되는 게 아닌 객체와 연관되어 있습니다.

왜냐하면 변수에 선언되는 데이터는 모두 객체이기 때문입니다.

만약 a = 10 이라는 python 구문이 있다면, 파이썬은 우선 10을 나타내는 객체를 생성합니다. 이후 변수 a를 생성하고 변수 a와 객체 10을 연결합니다.

즉, a와 객체 10은 다른 위치에 저장되고 이 둘은 참조에 의해 연결됩니다.

변수명 참조 주소 메모리 주소 객체 값

a 0x1 ➡️ 0x1 10

사실 이 부분을 좀 더 자세히 설명하면 다음과 같습니다.

interning

파이썬에서는 정수와 같은 몇몇 흔한 값들에 대해 미리 객체를 생성하고 재사용합니다. 이를 "interning"이라고 합니다. 특히 작은 정수에 대해 이런 방식이 적용됩니다.

구체적으로, -5부터 256까지의 정수 객체는 파이썬이 시작될 때 미리 생성되어 있습니다. 따라서 a = 10 구문을 실행할 때, 새로운 10 객체를 생성하는 것이 아니라 이미 생성되어 있는 10 객체를 재사용합니다.

따라서, a = 10을 실행하면 다음과 같은 순서로 동작합니다:

  1. 10 객체가 이미 메모리에 있는지 확인합니다.
  2. 이미 있으면 그 객체의 참조를 변수 a에 연결합니다.
  3. 없으면 (예를 들어, 매우 큰 숫자나 5 이하의 숫자에서는) 새로운 객체를 생성하고 그 참조를 변수 a에 연결합니다.

즉, a = 10 구문을 실행하면 a는 이미 존재하는 10 객체를 참조하게 됩니다.

10과 10은 같은 객체

앞에서 말한 interning은 파이썬 구문으로 확인 가능합니다.

파이썬에서 == 는 값이 같은지 확인하고 'is'는 같은 객체를 참조하고 있는지를 확인해 줍니다.

a = 10
b = 10
a == b
#--> True
a is b
#--> True

10은 같은 객체임을 확인할 수 있습니다.

얕은 복사, 깊은 복사

list_1 = [1,2,3]
list_2 = list_1
list_1[0] = 10
list_2
#--> [10,2,3]

list_1과 list_2는 같은 [1,2,3] 객체를 참조합니다.

그리고 list는 가변 객체이기에 값이 변할 수 있습니다.

List_1[0] = 10 이 구문은 list_1이 다른 객체를 참조하도록 하는 구문이 아니라, list_1이 참조하는 객체 자체를 바꾼 것입니다.

그러기에 같은 객체를 참조하는 list_2의 출력값도 바뀌는 것입니다.

표로 표현하면 다음과 같습니다.

변수명 참조 주소 메모리 주소 객체 값

list_1 0x1 ➡️ 0x1 [1,2,3]
list_2 0x1 ↗️    

list_1[0] = 10 구문 실행

변수명 참조 주소 메모리 주소 객체 값

list_1 0x1 ➡️ 0x1 [10,2,3]
list_2 0x1 ↗️    

다른 예시를 더 살펴보면

동적 타입 언어(dynamic typing)

a = 10
a = "ten"

이 경우 a의 타입이 정수타입에서 문자열타입으로 변경된 것이 아니라 변수 a가 객체 10을 참조했다가, 객체 "ten"을 참조하는 것이다.

왜냐하면 파이썬에서의 변수는 동적으로 타입을 갖습니다. 파이썬의 변수는 참조하는 객체의 타입에 따라 동적으로 타입이 바뀔 수 있습니다.

변수는 사실상 특정 객체를 참조하고 있기에 가능한 것입니다. 자바나, c에서는 불가능합니다. 왜냐하면 변수의 타입에 따라 메모리의 크기를 정했기 때문입니다.

객체 메모리 구성

파이썬의 객체는 메모리 상에서 여러 부분으로 구성되어 있습니다. 헤더에는 타입 정보와 다른 메타데이터가 저장되며, 이후에 실제 데이터 값 등이 저장됩니다.

객체는 타입 정보를 헤더에 저장합니다. 그래서 파이썬에서는 변수에 타입을 지정할 필요가 없습니다. 왜냐하면 각 객체가 자신의 타입 정보를 직접 포함하고 있기 때문입니다. 이 특징 덕분에 앞에서 말한 파이썬이 동적 타이핑 언어가 가능한 이유입니다.

객체의 메모리 구성

타입 정보 (헤더) (Type Information)
참조 카운트 (Reference Count)
객체의 실제 데이터 값 (Object’s Actual Data Value)

참조 카운트

참조 카운트는 파이썬의 메모리 관리 메커니즘 중 하나로, 특정 객체에 대한 참조의 수를 나타냅니다.

파이썬에서는 객체의 메모리를 효율적으로 관리하기 위해 참조 카운팅 방식을 사용합니다. 객체가 생성될 때 해당 객체의 참조 카운트는 1로 설정됩니다. 만약 다른 변수나 데이터 구조가 그 객체를 참조하게 되면 참조 카운트가 증가하며, 참조가 사라질 때마다 참조 카운트가 감소합니다.

참조 카운트가 0이 되면, 해당 객체는 더 이상 어떤 변수나 데이터 구조에도 참조되지 않는 것을 의미합니다. 이 시점에서 파이썬의 가비지 컬렉터(garbage collector)는 해당 객체의 메모리를 해제합니다.

a = [1, 2, 3]  # 리스트 객체 생성, 참조 카운트 = 1
b = a          # 리스트 객체에 대한 참조 추가, 참조 카운트 = 2

del a          # 리스트 객체에 대한 참조 제거, 참조 카운트 = 1
del b          # 리스트 객체에 대한 참조 제거, 참조 카운트 = 0 (이제 객체의 메모리가 해제될 수 있음)

 

'Python > Python' 카테고리의 다른 글

deque 모듈  (0) 2023.08.30
Python - isinstance()  (0) 2023.08.12
Python) 위치 인자, 키워드 인자  (0) 2023.07.25
@classmethod 와 @staticmethod  (0) 2023.07.20
@property 데코레이터  (0) 2023.07.01