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

[PYTHON] __slots__ 활용 방법으로 수백만 개 객체 메모리 부족 해결 및 성능 차이 분석 7가지 예제

by Papa Martino V 2026. 4. 12.
728x90

__slots__ 활용
__slots__ 활용

 

파이썬은 유연하고 강력한 언어이지만, 대규모 데이터를 처리할 때 '메모리 효율성'이라는 장벽에 부딪히곤 합니다. 특히 수백만 개 이상의 인스턴스를 생성해야 하는 데이터 분석, 시뮬레이션, 백엔드 캐싱 시스템에서 각 객체가 차지하는 기본 오버헤드는 시스템 전체의 성능 저하나 Out Of Memory(OOM) 에러를 유발하는 주범이 됩니다. 본 포스팅에서는 파이썬의 숨겨진 보물인 __slots__를 사용하여 객체 메모리 사용량을 획기적으로 줄이는 방법과 실무 해결 패턴을 심도 있게 다룹니다.


1. 파이썬 객체의 기본 구조와 __slots__의 등장 배경

기본적으로 파이썬의 모든 클래스 인스턴스는 __dict__라는 딕셔너리를 사용하여 속성(Attribute)을 저장합니다. 이 방식은 실행 중에 새로운 속성을 자유롭게 추가할 수 있는 유연성을 제공하지만, 딕셔너리 자료구조 자체가 가진 해시 테이블 오버헤드 때문에 상당한 메모리를 소모합니다.

__slots__는 클래스에 고정된 속성 이름 세트를 명시적으로 선언함으로써 __dict__의 생성을 방지합니다. 이를 통해 파이썬은 각 객체마다 딕셔너리를 만드는 대신, 정해진 크기의 고정된 구조체 형태로 데이터를 저장하게 됩니다.

일반 클래스와 __slots__ 클래스의 핵심 차이점

비교 항목 일반 클래스 (Default) __slots__ 적용 클래스
속성 저장 방식 __dict__ (가변 딕셔너리) 고정된 속성 슬롯 (Flat Array 구조)
메모리 오버헤드 매우 높음 (객체당 수백 바이트 추가) 매우 낮음 (필요한 데이터 크기만 점유)
속성 추가 유연성 런타임에 자유로운 속성 추가 가능 선언된 속성 외 추가 불가 (AttributeError)
접근 속도 상대적으로 느림 (딕셔너리 룩업) 상대적으로 빠름 (인덱스 기반 접근)
상속 특성 부모의 __dict__ 상속 자식에서 재선언하지 않으면 __dict__ 생성됨

2. 실무에서 즉시 적용 가능한 __slots__ 해결 패턴 Sample Examples

수백만 개의 객체를 다루는 실무 환경(API 서버, 로그 파서, 금융 데이터 처리 등)에서 유용하게 쓰이는 7가지 예제를 소개합니다.

Example 1: 수백만 개의 좌표 데이터를 관리하는 포인트 클래스

단순한 x, y 좌표를 가진 1,000,000개의 객체를 생성할 때 메모리 절감 효과를 극대화하는 기본 패턴입니다.

import sys

class PointRegular:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class PointSlots:
    __slots__ = ('x', 'y')  # 튜플로 속성 고정
    def __init__(self, x, y):
        self.x = x
        self.y = y

# 메모리 비교 예시
reg_objs = [PointRegular(i, i) for i in range(1000000)] # 약 150MB 이상 소모
slot_objs = [PointSlots(i, i) for i in range(1000000)]  # 약 50MB 미만 소모

Example 2: 상속 구조에서의 __slots__ 올바른 사용 방법

상속 관계에서 자식 클래스도 메모리 이득을 보려면 반드시 __slots__를 다시 선언해야 합니다.

class Base:
    __slots__ = ('id',)

class Derived(Base):
    __slots__ = ('name', 'email') # 부모의 'id'와 결합됨
    def __init__(self, id, name, email):
        self.id = id
        self.name = name
        self.email = email

# 자식 클래스에서 __slots__를 비워두면(__slots__ = ()) 
# 부모의 슬롯만 사용하며 __dict__ 생성을 막습니다.

Example 3: 대규모 로그 파싱 시스템에서의 데이터 객체 최적화

텍스트 로그를 객체화하여 메모리에 캐싱할 때 발생하는 속도와 용량 문제를 해결합니다.

class LogEntry:
    __slots__ = ('timestamp', 'level', 'message', 'ip_address')
    
    def __init__(self, ts, lvl, msg, ip):
        self.timestamp = ts
        self.level = lvl
        self.message = msg
        self.ip_address = ip

def process_logs(logs):
    # 수백만 줄의 로그를 처리해도 메모리 스와핑(Swapping)을 방지
    parsed_data = [LogEntry(*line.split('|')) for line in logs]
    return parsed_data

Example 4: __slots__와 Property를 결합한 유효성 검증 패턴

슬롯을 사용하면서도 @property를 통해 데이터 무결성을 보장할 수 있습니다.

class SecureUser:
    __slots__ = ('_user_id', '_status')
    
    def __init__(self, user_id):
        self._user_id = user_id
        self._status = 'active'
        
    @property
    def status(self):
        return self._status
    
    @status.setter
    def status(self, value):
        if value not in ['active', 'suspended']:
            raise ValueError("Invalid status")
        self._status = value

Example 5: 데이터베이스 ORM 결과 세트 모킹(Mocking) 최적화

DB 조인 결과를 리스트로 담아 처리할 때, 딕셔너리보다 가벼운 슬롯 객체를 사용해 API 응답 속도를 향상시킵니다.

class DBRecord:
    __slots__ = ('pk', 'data', 'created_at')
    
    def __init__(self, pk, data, created_at):
        self.pk = pk
        self.data = data
        self.created_at = created_at

# 수천 명의 동시 접속자가 대량의 데이터를 요청할 때 서버 RAM 부하 경감

Example 6: WeakRef 지원을 포함한 __slots__ 구성 방법

슬롯을 쓰면 약한 참조(Weakref)가 기본적으로 불가능하지만, '__weakref__'를 추가하여 해결할 수 있습니다.

import weakref

class ObservableObject:
    __slots__ = ('name', '__weakref__') # weakref 지원을 위한 특수 슬롯
    
    def __init__(self, name):
        self.name = name

obj = ObservableObject("MemorySafe")
r = weakref.ref(obj)
print(r().name) # 정상 접근 가능

Example 7: 동적 속성 추가가 필요한 경우의 절충안

일부 속성은 고정하고, 나머지는 자유롭게 추가하고 싶을 때 '__dict__'를 슬롯에 포함하는 트릭입니다.

class HybridClass:
    __slots__ = ('fixed_attr', '__dict__') # 고정 속성 외 나머지는 dict에 저장
    
    def __init__(self, fixed_val):
        self.fixed_attr = fixed_val

h = HybridClass("static")
h.dynamic_attr = "I am dynamic" # 에러 발생 안 함
# 주의: 이 경우 __dict__가 생성되므로 메모리 절감 효과는 일부 희석됨

3. 주의사항 및 가이드라인

__slots__는 마법의 도구가 아닙니다. 다음과 같은 상황에서는 사용을 재고해야 합니다.

  • 다중 상속의 복잡성: 여러 부모 클래스가 모두 슬롯을 가지고 있으면 충돌이 발생하기 쉽습니다.
  • 고정된 스키마: 객체 생성 후 새로운 속성을 할당해야 하는 동적 로직이 많다면 슬롯은 오히려 AttributeError를 유발하는 장애물이 됩니다.
  • 객체 수가 적은 경우: 수천 개 정도의 객체라면 절약되는 메모리 양보다 코드 복잡성 증가로 인한 손해가 더 클 수 있습니다.

4. 결론

파이썬의 __slots__는 단순한 메모리 최적화 기법을 넘어, 대규모 데이터 처리를 위한 아키텍처 설계의 필수 요소입니다. 특히 클라우드 환경에서는 메모리 점유율이 곧 비용과 직결되므로, 데이터 구조체 역할을 하는 클래스에는 반드시 슬롯을 적용하는 습관을 들이는 것이 좋습니다. 위 7가지 예제를 바탕으로 여러분의 애플리케이션에서 발생하는 메모리 병목 현상을 해결해 보시기 바랍니다.

 

[내용 출처 및 참고 문헌]

  • Python Documentation: "The Python Language Reference - __slots__"
  • Raymond Hettinger, "Python's Class Development Toolkit" (PyCon).
  • High Performance Python, 2nd Edition (O'Reilly) - Chapter 11.
  • Real Python, "Python Slots: Improve Performance and Memory Usage."
728x90