
파이썬(Python)은 데이터 과학과 머신러닝 분야에서 가장 사랑받는 언어이지만, 대규모 데이터를 다룰 때 '메모리 관리'라는 고질적인 숙제를 안겨주기도 합니다. 특히 데이터를 가공하고 변형하는 과정에서 우리는 본능적으로 List Comprehension을 사용하곤 합니다. 하지만 데이터의 크기가 기가바이트(GB) 단위로 넘어가면 시스템은 어느새 MemoryError를 뿜어내며 멈춰버립니다.
오늘 이 글에서는 파이썬 개발자라면 반드시 마주하게 될 List Comprehension과 Generator의 메모리 점유율 차이를 심층 분석하고, 실무에서 어떤 시점에 각각의 기법을 배치해야 프로젝트의 안정성을 확보할 수 있는지 7가지 이상의 실무 사례와 함께 상세히 다루겠습니다.
1. 데이터 처리 방식의 근본적인 철학 차이
List Comprehension은 'Eager Evaluation(조급한 평가)' 방식을 따릅니다. 즉, 명령이 내려지는 즉시 메모리에 모든 결과값을 생성하여 리스트 객체로 올립니다. 반면, Generator는 'Lazy Evaluation(느긋한 평가)' 방식을 채택합니다. 값이 실제로 필요한 시점(Iteration)이 되어서야 비로소 다음 요소를 계산하여 반환합니다. 이 작은 차이가 수백만 개의 데이터를 처리할 때 시스템 전체의 생존을 결정짓는 핵심 요소가 됩니다.
2. 메모리 점유율 및 성능 비교 요약
두 방식의 핵심적인 차이를 한눈에 파악할 수 있도록 표로 정리했습니다. 대규모 데이터셋(예: 1억 건의 정수 데이터)을 처리할 때의 가상 지표를 포함합니다.
| 항목 | List Comprehension | Generator Expression |
|---|---|---|
| 평가 방식 | 즉시 실행 (Eager) | 지연 실행 (Lazy) |
| 메모리 사용량 | 데이터 크기에 비례하여 증가 (O(n)) | 상수 수준 유지 (O(1)) |
| 반환 객체 | List | Generator Object (Iterator) |
| 접근 속도 | 인덱싱 가능, 전체 접근 시 빠름 | 순차 접근만 가능, 초기 응답 속도 매우 빠름 |
| 재사용성 | 여러 번 순회 가능 | 한 번 사용하면 소모됨 (Exhaustible) |
| 적합한 사례 | 필터링 후 데이터를 반복해서 써야 할 때 | 데이터가 너무 커서 메모리에 담을 수 없을 때 |
3. 실무 적용을 위한 7가지 개발자 샘플 코드 (Example)
실제 엔지니어링 현장에서 발생할 수 있는 시나리오를 바탕으로 코드 예제를 구성했습니다.
Ex 1. 메모리 사용량 직접 측정하기 (Basic Comparison)
import sys
# 100만 개의 숫자를 생성할 때
list_comp = [i for i in range(1000000)]
gen_exp = (i for i in range(1000000))
print(f"List Comprehension Size: {sys.getsizeof(list_comp)} bytes")
print(f"Generator Expression Size: {sys.getsizeof(gen_exp)} bytes")
# 결과: List는 약 8MB 이상, Generator는 약 112바이트 고정
Ex 2. 대용량 로그 파일 한 줄씩 읽어 처리하기
def process_logs(file_path):
# Generator를 사용해 파일을 한 줄씩 스트리밍
log_lines = (line.strip() for line in open(file_path, 'r'))
error_logs = (line for line in log_lines if "ERROR" in line)
for error in error_logs:
# 이 시점에만 메모리에 한 줄씩 로드됨
send_alert(error)
def send_alert(msg):
print(f"Alert: {msg}")
Ex 3. 데이터베이스 쿼리 결과 파이프라인 구축
# 수천만 건의 DB 레코드를 처리할 때 메모리 고갈 방지
def fetch_large_data(cursor):
while True:
row = cursor.fetchone()
if not row:
break
yield row
# 비즈니스 로직 적용
raw_data = fetch_large_data(my_cursor)
cleaned_data = (format_row(row) for row in raw_data)
final_result = (validate(d) for d in cleaned_data if d.is_active)
for item in final_result:
save_to_csv(item)
Ex 4. 무한 수열(Infinite Sequence) 생성
def infinite_counter():
n = 0
while True:
yield n
n += 1
# List로는 구현 불가능한 무한 루프 제어
for count in infinite_counter():
if count > 1000: break
print(count)
Ex 5. 다중 필터링 시 메모리 효율 극대화
# 복잡한 전처리 과정을 List로 연결하면 매 단계마다 새 리스트가 생성됨
data = range(10000000)
# 효율적인 Generator 파이프라인
step1 = (x * 2 for x in data)
step2 = (x for x in step1 if x % 3 == 0)
step3 = (f"Value: {x}" for x in step2)
# 마지막에 필요한 만큼만 소비
print(next(step3))
Ex 6. 메모리 내 요소를 즉시 합산 처리 (Memory-Efficient Sum)
# 리스트 전체를 만들지 않고 합산만 수행
# sum([]) 보다 sum(())이 대규모 데이터에서 훨씬 안전함
total_sum = sum(i ** 2 for i in range(10000000) if i % 2 == 0)
print(total_sum)
Ex 7. AI 모델 추론을 위한 배치(Batch) 생성기
def batch_generator(data_list, batch_size):
for i in range(0, len(data_list), batch_size):
# 슬라이싱 결과를 yield하여 메모리 효율 확보
yield data_list[i:i + batch_size]
large_dataset = [i for i in range(10000)] # 원본 데이터
for batch in batch_generator(large_dataset, 32):
model_inference(batch)
4. 해결책 및 결론: 언제 무엇을 쓸 것인가?
단순히 'Generator가 항상 좋다'는 것은 오해입니다. 데이터의 성격과 비즈니스 로직에 따라 다음과 같이 선택하십시오.
- List Comprehension을 선택해야 하는 경우:
- 데이터의 크기가 메모리에 충분히 올라갈 정도로 작을 때
- 생성된 데이터를 인덱싱(Indexing)하거나 슬라이싱(Slicing)해야 할 때
- 해당 리스트를 프로그램 내에서 여러 번 반복해서 순회해야 할 때
- 속도가 메모리 효율보다 중요할 때 (작은 데이터셋에서는 List가 미세하게 빠름)
- Generator를 선택해야 하는 경우:
- 데이터셋의 크기를 가늠할 수 없거나 메모리보다 큰 대용량일 때
- 데이터를 단 한 번만 순회하며 처리하면 될 때
- 각 요소의 처리 비용이 커서 결과를 점진적으로 확인해야 할 때 (Lazy Loading)
- 중간 가공 단계가 많아 매 단계 리스트를 생성하기 부담스러울 때
결론적으로, 현대적인 파이썬 아키텍처에서는 'Generator-First' 전략을 권장합니다. 먼저 Generator로 파이프라인을 설계한 뒤, 성능 병목이 발생하거나 임의 접근(Random Access)이 필요한 시점에만 list() 함수를 호출하여 메모리에 적재하는 방식이 가장 안전하고 효율적인 해결책입니다.
참고 출처
- Python Software Foundation - Functional Programming Modules (itertools)
- PEP 289 – Generator Expressions
- Fluent Python by Luciano Ramalho (O'Reilly Media)
- High Performance Python by Micha Gorelick & Ian Ozsvald