들어가며
파이썬을 사용하다 보면, 대용량 데이터를 효율적으로 처리하거나 무한 시퀀스를 다루고 싶을 때 제너레이터(generator) 가 빛을 발합니다. 제너레이터는 한 번에 모든 값을 메모리에 올리지 않고, 필요할 때마다 한 개씩 생성한다는 점에서 메모리 사용량을 크게 줄여주는 장점이 있습니다. 이 글에서는 yield 키워드의 기본 개념부터 심화 주제인 yield from, 제너레이터 표현식, 그리고 실전 파이프라인 예시까지 차근차근 살펴보겠습니다.
1. 제너레이터란?
- 정의
일반 함수와는 달리, yield 키워드를 포함한 함수를 제너레이터 함수라고 부릅니다. 이 함수를 호출하면 제너레이터 객체(generator object)가 반환되며, 이 객체는 이터레이터(iterator)로 동작합니다. - 특징
- 지연 평가(Lazy Evaluation): 값이 필요할 때마다 생성 → 메모리 절약
- 상태 보존(Stateful): yield로 멈춘 위치와 지역 변수 상태를 그대로 유지
- 이터레이터 프로토콜: __iter__(), __next__() 메서드를 지원 → for 루프와 호환
2. yield 기본 사용법
def simple_gen():
print("첫 번째 yield 직전")
yield "A"
print("두 번째 yield 직전")
yield "B"
print("완료!")
gen = simple_gen()
print(next(gen)) # 첫 번째 yield 직전 → "A"
print(next(gen)) # 두 번째 yield 직전 → "B"
# 이후 next(gen)를 호출하면 StopIteration 발생
- simple_gen() 호출 → 제너레이터 객체 생성
- next(gen) → 첫 yield까지 실행, "A" 반환
- 다시 next(gen) → 두 번째 yield까지 실행, "B" 반환
3. yield from 심화
복수의 이터러블을 한꺼번에 이어서 처리할 때 yield from을 사용하면 코드를 간결하게 작성할 수 있습니다.
def chain(*iterables):
yield from iterables[0]
yield from iterables[1]
# 또는:
# for it in iterables:
# yield from it
# 사용 예
print(list(chain([1,2], "AB", range(3))))
# 출력: [1, 2, 'A', 'B', 0, 1, 2]
- 내부 이터러블의 요소를 차례로 꺼내 yield
- for 루프 없이 중첩 이터러블 처리 가능
4. 제너레이터 표현식
리스트 컴프리헨션과 거의 같은 문법이지만, 괄호를 [] 대신 ()로 바꾸면 제너레이터 표현식이 됩니다.
# 리스트 컴프리헨션
squares_list = [x*x for x in range(1_000_000)]
# 제너레이터 표현식
squares_gen = (x*x for x in range(1_000_000))
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
# … 계속 메모리 부담 없이 사용 가능
5. 실전 예제: 파이프라인 처리
여러 단계를 거쳐 데이터를 처리할 때, 각 단계를 제너레이터로 구현하면 매우 깔끔합니다.
# 1) 무한 시퀀스 생성
def integers(start=1):
n = start
while True:
yield n
n += 1
# 2) 짝수 필터
def evens(seq):
for x in seq:
if x % 2 == 0:
yield x
# 3) 제곱 계산
def squares(seq):
for x in seq:
yield x * x
# 파이프라인 연결
gen = squares(evens(integers(1)))
for _ in range(5):
print(next(gen))
# 출력: 4, 16, 36, 64, 100
- integers: 무한히 증가하는 숫자 생성
- evens: 짝수만 필터링
- squares: 제곱 계산
- 중간 결과를 리스트로 저장하지 않고 스트리밍 처리 가능
6. 제너레이터 제어: close()와 throw()
- gen.close(): 제너레이터를 강제 종료하고 GeneratorExit 예외를 발생시킵니다.
- gen.throw(exc_type): 제너레이터 내부에 예외를 던져, 내부에서 예외 처리 로직을 수행할 수 있습니다.
def foo():
try:
yield 1
except ValueError:
print("ValueError 처리됨!")
yield 2
g = foo()
print(next(g)) # 1
print(g.throw(ValueError)) # "ValueError 처리됨!" 출력, 이후 2 반환
7. 언제 사용하면 좋을까?
- 대용량 데이터 스트리밍: 로그 파일, 대규모 CSV 등
- 네트워크 데이터 처리: 소켓 스트림, API 응답 청크
- 무한 시퀀스 생성: 시뮬레이션, 랜덤 넘버 생성
- 메모리 최적화: 전체 데이터를 한 번에 메모리에 올리기 어려울 때
마무리
yield와 제너레이터는 파이썬의 강력한 기능 중 하나로, 복잡한 데이터 파이프라인을 간결하고 메모리 효율적으로 구현할 수 있게 해 줍니다. 본 가이드를 바탕으로 다양한 상황에 제너레이터를 응용해 보시고, 스트리밍 처리나 무한 시퀀스 생성 등에서 그 진가를 느껴보세요!
'python' 카테고리의 다른 글
| PowerShell에서 Python Poetry 설치 및 PyPI 배포 가이드 (0) | 2025.04.29 |
|---|---|
| 🔧 VS Code에서 Poetry 가상환경 제대로 연동하는 방법 (pyenv + launch.json 설정 포함) (0) | 2025.04.29 |
| [python] 중첩 함수(nested function) (0) | 2024.04.09 |
| [python] first-class object (일급 객체) (0) | 2024.04.09 |
| python Unlimited Arguments(무제한 인자) (0) | 2024.04.08 |