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

[PYTHON] 대용량 데이터 처리 시 List 대신 Generator를 써야 하는 3가지 이유와 메모리 절약 방법 7가지

by Papa Martino V 2026. 3. 30.
728x90

List 대신 Generator
List vs Generator

 

파이썬은 데이터 과학과 백엔드 개발에서 가장 사랑받는 언어 중 하나입니다. 하지만 수천만 개의 행을 가진 로그 파일을 읽거나 대규모 데이터베이스 쿼리 결과를 처리할 때, 무심코 사용한 List(리스트)는 시스템의 RAM을 순식간에 점유하여 MemoryError를 발생시키거나 시스템 전체를 느리게 만드는 주범이 됩니다. 이러한 치명적인 성능 저하를 방지하기 위한 핵심 솔루션이 바로 Generator(제너레이터)입니다. 본 포스팅에서는 리스트와 제너레이터의 구조적 차이점과 실무 환경에서 메모리를 극단적으로 아끼는 7가지 실전 예제를 소개합니다.


1. List vs Generator: 메모리 점유와 처리 방식의 근본적 차이

리스트는 모든 데이터를 메모리에 미리 올려두는 Eager Evaluation(조급한 계산) 방식을 취합니다. 반면, 제너레이터는 데이터가 필요한 순간에만 값을 생성하여 반환하는 Lazy Evaluation(지연 계산) 방식을 사용합니다. 1GB 크기의 텍스트 파일을 리스트로 읽으면 1GB 이상의 RAM이 필요하지만, 제너레이터로 읽으면 단 몇 KB의 메모리만으로도 충분합니다.

비교 항목 List (리스트) Generator (제너레이터)
데이터 생성 방식 모든 요소를 한꺼번에 생성하여 저장 요청 시점에 하나씩 생성 (yield)
메모리 사용량 데이터 크기에 비례하여 증가 ($O(n)$) 데이터 크기와 상관없이 일정함 ($O(1)$)
성능 (초기화 속도) 모든 객체 생성 시간이 소요됨 (느림) 즉시 생성됨 (매우 빠름)
데이터 접근 방식 인덱싱 가능, 반복 재사용 가능 인덱싱 불가, 일회성 소비 (One-time)

2. 실무에 바로 적용하는 제너레이터 활용 예제 7가지

단순한 이론을 넘어, 현업 개발자가 대규모 시스템에서 메모리 릭(Memory Leak)을 방지하고 응답 속도를 개선하기 위해 즉시 적용할 수 있는 구체적인 코드를 제시합니다.

Example 1: 대용량 텍스트 파일 라인 단위 읽기

파일 전체를 메모리에 올리지 않고 한 줄씩 읽어 처리하는 가장 표준적인 방법입니다.


def stream_large_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            yield line.strip()

# 수 기가바이트 파일도 메모리 문제 없이 처리 가능
for log_entry in stream_large_file("huge_access_log.txt"):
    if "ERROR" in log_entry:
        print(log_entry)

Example 2: Generator Expression을 통한 메모리 절약

List Comprehension 대신 소괄호()를 사용하여 간결하게 제너레이터를 생성합니다.


import sys

# List Comprehension: 모든 숫자를 메모리에 생성
list_comp = [x ** 2 for x in range(1000000)]
print(f"List size: {sys.getsizeof(list_comp)} bytes")

# Generator Expression: 규칙만 저장하고 값은 나중에 생성
gen_exp = (x ** 2 for x in range(1000000))
print(f"Generator size: {sys.getsizeof(gen_exp)} bytes") # 극적인 차이 발생

Example 3: 무한 수열(Infinite Sequence) 생성

메모리 한계를 벗어나 끝이 없는 데이터 스트림을 시뮬레이션할 때 유용합니다.


def infinite_counter():
    n = 0
    while True:
        yield n
        n += 1

counter = infinite_counter()
# 필요한 만큼만 가져다 쓰기
for _ in range(5):
    print(next(counter))

Example 4: yield from을 이용한 제너레이터 계층화

여러 개의 데이터 소스를 하나로 합쳐서 스트리밍할 때 코드의 가독성을 높여줍니다.


def get_log_2024():
    yield "2024-01-01: OK"
    yield "2024-01-02: FAIL"

def get_log_2025():
    yield "2025-01-01: SUCCESS"

def total_logs():
    yield from get_log_2024()
    yield from get_log_2025()

for log in total_logs():
    print(log)

Example 5: 파이프라인(Pipeline) 설계를 통한 데이터 전처리

각 단계를 제너레이터로 연결하면 중간 결과물이 메모리에 쌓이지 않습니다.


def read_raw_data(data_list):
    for item in data_list:
        yield item

def filter_positive(numbers):
    for n in numbers:
        if n > 0:
            yield n

def multiply_by_ten(numbers):
    for n in numbers:
        yield n * 10

# 파이프라인 구축 (실행 전까지는 어떤 계산도 하지 않음)
raw = read_raw_data([-5, 2, -1, 10, 0])
filtered = filter_positive(raw)
final = multiply_by_ten(filtered)

print(list(final)) # [20, 100]

Example 6: 대규모 DB 쿼리 결과 페이징 처리

ORM 사용 시 수만 건의 데이터를 all()로 가져오는 대신 제너레이터 방식으로 가져옵니다.


# SQLAlchemy 등의 ORM 예시 개념 코드
def fetch_users_streamingly(session):
    # .all() 대신 yield를 사용하는 fetch 방식 적용
    query = session.query(User).yield_per(100) 
    for user in query:
        yield user.email

# 서버 메모리 부하 없이 사용자 이메일 발송 루프 가능
for email in fetch_users_streamingly(db_session):
    send_email(email)

Example 7: next() 함수의 기본값 활용을 통한 에러 해결

데이터가 소진되었을 때 StopIteration 에러를 방지하고 안전하게 다음 값을 가져오는 방법입니다.


my_gen = (x for x in range(2))

print(next(my_gen, "End of data")) # 0
print(next(my_gen, "End of data")) # 1
print(next(my_gen, "End of data")) # End of data (에러 없이 출력)

3. 언제 Generator를 사용하지 않아야 하는가?

제너레이터가 항상 정답은 아닙니다. 데이터의 크기가 충분히 작거나, 동일한 데이터를 여러 번 다시 조회(Re-use)해야 하는 경우라면 리스트가 더 적합합니다. 제너레이터는 한 번 끝까지 돌면 비워지기 때문에 다시 사용하려면 제너레이터 객체를 새로 생성해야 하는 비용이 발생하기 때문입니다.


4. 결론: 효율적인 파이썬 개발을 위한 제언

현대적인 파이썬 프로그래밍에서 성능 최적화의 핵심은 "어떻게 하면 데이터를 적게 들고 있을까?"에 달려 있습니다. 대규모 트래픽을 견뎌야 하는 백엔드 API나 기가바이트 단위의 데이터 파이프라인을 설계한다면, 리스트 대신 제너레이터를 적극 도입하십시오. 이는 단순한 코딩 스타일의 차이를 넘어 서비스의 안정성과 인프라 비용 절감으로 이어지는 가장 확실한 방법입니다.


내용 출처 및 참고 문헌

  • Python Software Foundation - 공식 문서: Generators and Iterators
  • Fluent Python (Luciano Ramalho 저) - Chapter 14: Iterables, Iterators, and Generators
  • Real Python - How to Use Generators and yield in Python
  • Effective Python (Brett Slatkin 저) - Item 30: Consider Generators Instead of Returning Lists
728x90