본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 파이썬의 심장 PyObject 구조체 : 객체 표현 방식과 메모리 효율을 높이는 3가지 해결 방법

by Papa Martino V 2026. 2. 27.
728x90

PyObject 구조체
PyObject 구조체

 

파이썬은 "모든 것이 객체(Everything is an Object)"인 언어입니다. 우리가 흔히 사용하는 정수, 문자열, 심지어 함수와 클래스조차도 내부적으로는 객체로 관리됩니다. 이 거대한 객체 지향 시스템의 뿌리에는 바로 PyObject라는 C 언어 구조체가 존재합니다. 본 글에서는 CPython 소스 코드 레벨에서 PyObject가 어떻게 설계되었는지, 그리고 이것이 파이썬의 동적 타이핑과 메모리 관리에 어떤 차이를 만드는지 심층적으로 탐구합니다.


1. PyObject란 무엇인가? 파이썬 객체의 기본 설계도

파이썬의 표준 구현체인 CPython에서 모든 객체는 PyObject 구조체를 확장한 형태를 가집니다. 파이썬 변수가 실제로 데이터를 담는 방식은 단순한 메모리 할당을 넘어, 해당 데이터의 타입 정보와 참조 횟수를 관리하는 메커니즘을 포함합니다.

PyObject의 핵심 구성 요소

  • ob_refcnt (Reference Count): 객체가 참조되고 있는 횟수를 나타내는 정수입니다. 이 값이 0이 되면 가비지 컬렉터(GC)에 의해 메모리에서 해제됩니다.
  • ob_type (Type Object Pointer): 해당 객체가 어떤 타입(int, str, list 등)인지 정의하는 _typeobject 구조체를 가리키는 포인터입니다.

2. 고정 크기 객체와 가변 크기 객체의 차이점 (PyObject vs PyVarObject)

파이썬 객체는 크게 크기가 고정된 객체와 데이터 양에 따라 크기가 변하는 객체로 나뉩니다. 이 두 구조의 차이를 이해하는 것이 파이썬 내부 동작을 이해하는 첫걸음입니다.

항목 PyObject (기본형) PyVarObject (가변형)
대상 객체 정수(Fixed-size), 사용자 정의 클래스 인스턴스 리스트(list), 튜플(tuple), 문자열(str), 딕셔너리(dict)
추가 멤버 없음 (refcnt, type만 존재) ob_size (요소의 개수를 저장)
메모리 할당 생성 시 고정된 크기 할당 데이터 추가/삭제에 따라 가변적으로 관리
주요 특징 데이터 값이 객체 본체 내부에 포함됨 데이터 포인터 또는 연속된 메모리 블록 관리

3. PyObject가 파이썬 성능에 미치는 영향과 3가지 최적화 방법

모든 데이터가 객체로 표현된다는 점은 유연성을 주지만, 메모리 오버헤드라는 단점도 가집니다. 예를 들어, C 언어의 4바이트 정수형과 달리 파이썬의 정수 객체는 최소 28바이트 이상의 메모리를 점유합니다. 이를 해결하기 위한 내부 메커니즘과 개발자가 취할 수 있는 방법은 다음과 같습니다.

방법 1: 정수 및 문자열 인터닝 (Interning)

자주 사용되는 작은 정수(-5 ~ 256)나 짧은 문자열을 미리 생성하여 재사용함으로써 PyObject 생성 오버헤드를 줄입니다.

방법 2: __slots__를 활용한 메모리 절약

일반적인 파이썬 클래스 인스턴스는 내부 속성을 저장하기 위해 __dict__(딕셔너리 객체)를 가집니다. __slots__를 정의하면 PyObject 구조에 속성 공간을 직접 할당하여 딕셔너리 생성을 방지하고 메모리를 획기적으로 줄일 수 있습니다.

방법 3: 대량 데이터 처리 시 NumPy 활용

수백만 개의 정수를 PyObject 리스트로 관리하는 대신, C 언어 수준의 연속된 메모리 배열을 사용하는 NumPy를 사용하면 PyObject의 무거운 헤더(refcnt, type) 비용을 제거할 수 있습니다.


4. Sample Example: C 수준에서의 PyObject 시뮬레이션

파이썬 내부에서 객체가 어떻게 연결되는지 개념적으로 이해하기 위한 의사 코드(Pseudo-code) 예시입니다.


/* CPython의 실제 구조를 간략화한 예시 */
typedef struct _object {
    ssize_t ob_refcnt;        // 참조 횟수 관리
    struct _typeobject *ob_type; // 객체 타입 정보
} PyObject;

typedef struct {
    PyObject ob_base;         // 모든 가변 객체는 PyObject를 포함 (상속 구조)
    ssize_t ob_size;          // 요소 개수 (리스트의 길이 등)
} PyVarObject;

/* 정수 객체의 실제 내부 모습 */
struct _longobject {
    PyObject ob_base;
    uint32_t ob_digit[1];     // 실제 정수 값을 저장하는 가변 배열
};

5. 결론: 왜 PyObject를 알아야 하는가?

PyObject의 구조를 이해하면 파이썬의 동적 타이핑이 왜 가능한지(모든 객체가 공통 헤더를 가지므로 동일한 포인터 타입으로 취급 가능), 왜 메모리 사용량이 많은지를 명확히 알 수 있습니다. 이는 고성능 파이썬 코드를 작성하거나 C 확장 모듈을 개발할 때 필수적인 전문 지식입니다.


6. 내용의 출처 및 참고 문헌

  • CPython GitHub Repository: Include/object.h 소스 코드 분석
  • Python Software Foundation (PSF) 공식 문서: "Python/C API Reference Manual"
  • "Fluent Python" by Luciano Ramalho (O'Reilly Media)
  • "Inside the Python Virtual Machine" by Obi Ike-Nwosu
728x90