Python 강좌 / 이터레이터와 제너레이터로 데이터를 순서대로 처리하기

이터레이터는 다음 항목을 하나씩 제공하는 객체이고 제너레이터는 yield로 그런 동작을 작성하는 방법입니다. 필요한 시점에 값을 계산할 수 있지만 한 번 소비한 결과를 자동으로 처음부터 다시 읽을 수 있는 것은 아닙니다.

반복 가능 객체와 이터레이터

리스트는 반복 가능한 객체이며 iter(list)는 그 목록을 읽는 이터레이터를 만듭니다. next로 다음 항목을 요청하고 더 없으면 StopIteration이 발생합니다. for는 이 종료 신호를 처리해 반복을 끝냅니다.

관련 동작과 지원 조건은 이터레이터와 제너레이터로 데이터를 순서대로 처리하기 관련 공식 문서에서 확인할 수 있습니다.

yield가 들어 있는 함수를 호출하면 본문을 끝까지 실행하는 대신 제너레이터 객체를 만듭니다. 다음 값을 요청할 때 본문을 진행하고 yield 위치에서 상태를 보존하며 멈춥니다.

전체 예제와 실행 결과

별도의 실습 폴더에서 아래 파일을 UTF-8로 저장합니다. 가상환경을 선택한 터미널에서 python generators.py 명령으로 실행합니다. macOS·Ubuntu에서 가상환경을 사용하지 않는 경우 명령 이름을 python3으로 바꿉니다.

generators.py 파일의 전체 내용입니다.

def running_totals(amounts):
    total = 0
    for amount in amounts:
        total += amount
        yield total

values = running_totals([3500, 8000, 2500])
print(next(values))
print(list(values))
print(list(values))
print(list(running_totals([3500, 8000, 2500])))

예상 결과는 다음과 같습니다.

3500
[11500, 14000]
[]
[3500, 11500, 14000]

지연 계산과 메모리의 실제 범위

전체 누적 결과를 리스트로 미리 만들지 않고 요청할 때 생성하므로 결과 보관 메모리를 줄일 수 있습니다. 하지만 입력 자체가 이미 큰 리스트라면 그 입력 메모리는 여전히 필요합니다. list(generator)로 모두 모으면 결과도 메모리에 저장됩니다.

제너레이터 표현식은 (변환식 for 항목 in 모음) 형태입니다. 여러 번 읽어야 하는 작은 결과는 리스트로 저장하는 편이 단순할 수 있습니다. 지연 계산은 무조건 더 빠르다는 보장이 아니라 평가 시점과 보관 방식을 바꾸는 선택입니다.

소비 시점의 오류와 자원 수명

제너레이터 본문에서 나는 예외는 객체를 만든 시점이 아니라 값을 소비하는 도중 발생할 수 있습니다. 파일을 여는 제너레이터는 일부만 읽고 중단했을 때 파일이 언제 닫히는지까지 설계해야 합니다. with로 파일 수명을 관리하는 호출 구조를 검토하세요.

  1. next를 한 번 더 호출하면 남은 결과가 어떻게 바뀌는지 확인합니다.
  2. 입력을 빈 리스트로 바꾸어 처음 next에서 종료 신호가 발생하는지 확인합니다.
  3. 두 번 읽으려면 새 제너레이터를 만들거나 결과를 저장해야 함을 코드로 확인합니다.