← 기술 지식

기술 지식 / 프로그래밍 언어 / Python / 객체·프로토콜

Python Iterator의 소비 상태와 Yield From 제어

iterable·iterator·generator의 재순회 차이와 지연 실행·send·throw·close·반환값·tee 버퍼·자원 수명을 설명합니다.

언어·런타임 · 원리·구현·운영
기술 지식 트리

Python의 iterable은 반복할 수 있다는 인터페이스이고, iterator는 현재 소비 위치를 가진 객체이며, generator는 그 상태를 지연 실행하는 한 구현입니다. 따라서 같은 데이터를 다시 순회할 수 있는지, next·send·throw·close가 어느 객체로 전달되는지, yield 사이에 자원이 얼마나 오래 살아 있는지를 구분해서 읽어야 합니다.

Generator 호출과 Next 호출의 실행 시점

def numbers():
    print('start')
    yield 1
    yield 2

it = numbers()
print('created')
print(next(it))
print(list(it))
print(list(it))
# created, start, 1, [2], []

generator 함수 호출은 generator 객체를 만들고 본문 실행은 next 등으로 값을 요구할 때 시작합니다. generator 객체는 현재 실행 위치와 지역 상태를 보관하는 iterator입니다. 한 번 소진한 객체를 다시 순회하면 처음부터 재생되지 않습니다.

상태를 iter(list)→next 한 번→list(iterator)→list(iterator)로 기록하면 결과가 각각 현재 위치를 반영한다는 것을 확인할 수 있습니다. 같은 파일을 다시 여는 factory는 새 iterator를 만들지만 같은 snapshot을 보장하지 않으므로, “재순회 가능”과 “같은 결과 재생”을 API 계약에서 별도로 적습니다.

Iterable 재순회와 Iterator 소비 상태

대상 iter 호출 재순회
list 보통 새 iterator 생성 같은 목록을 다시 순회 가능
iterator 자기 자신 반환 현재 소비 위치부터
generator 객체 자기 자신인 iterator 소진하면 끝
generator factory 호출마다 새 generator 원본 데이터가 같다는 조건은 별도

iterator는 __next__()로 값을 내놓고 끝에서 StopIteration을 일으킵니다. iterable은 iter()를 호출할 수 있다는 뜻일 뿐 매번 새로운 snapshot을 제공한다는 보장은 아니므로, API가 단일 소비 입력인지 반복 사용을 전제로 하는지 먼저 정합니다. 입력을 두 번 읽어야 하면 물질화·factory·재사용 가능한 자료구조 중 필요한 방식을 요구합니다.

factory가 파일·DB를 다시 읽으면 두 번째 결과가 달라질 수 있습니다. 같은 snapshot의 재생과 단순 재계산은 다릅니다. itertools.tee는 소비자를 나누지만 느린 소비자를 위해 값을 버퍼링하므로 속도 차이가 크면 메모리가 늘 수 있습니다.

Yield From의 값 전달·제어 위임

def child():
    value = yield 'ready'
    return value * 2

def parent():
    result = yield from child()
    yield result

it = parent()
print(next(it))    # ready
print(it.send(3))  # 6

child가 return value * 2로 끝나면 그 값은 StopIteration.value에 담기고, yield from child() 표현식의 결과로 parent에 돌아옵니다. 반면 for value in child(): yield value는 yield된 값만 전달할 뿐 이 반환값과 send·throw·close의 위임을 같은 방식으로 처리하지 않습니다.

Yield From은 하위 Generator와 제어를 주고받습니다호출자. parent의 yield from. child generatornext·send·throw·close지원 제어 위임yield·종료 결과호출자parent의 yield fromchild generator
Yield From은 하위 Generator와 제어를 주고받습니다 아래 화살표는 next·send 등 제어 전달, 되돌아오는 화살표는 yield 값과 종료 반환값입니다. 하위 iterator가 지원하는 메서드에 따라 위임 규칙이 적용됩니다.
그림을 글로 읽기
  • 호출자
  • parent의 yield from
  • child generator
  1. 호출자 → parent의 yield from: next·send·throw·close
  2. parent의 yield from → child generator: 지원 제어 위임
  3. child generator → 호출자: yield·종료 결과

새 generator는 먼저 next()나 send(None)을 호출해 첫 yield까지 진입해야 하며, 처음부터 non-None 값을 send하면 오류입니다. yield from에 들어온 예외는 하위 iterator가 throw를 지원하는지에 따라 전달되고, close도 하위에 close가 있으면 정리를 연결합니다. 일반 iterator 모두가 send·throw를 제공하는 것은 아닙니다.

조기 종료와 Generator 자원 수명

generator가 파일·DB 연결을 보유한 채 yield하면 소비자가 다음 값을 요구하기 전까지 그 자원이 남을 수 있습니다. for 루프를 break했다고 모든 임의 iterator의 close가 자동 호출된다고 일반화하지 않습니다. 명시적인 close나 contextlib.closing·자원 범위 API로 조기 종료를 관리합니다.

close는 정지한 generator에 GeneratorExit를 전달해 finally 정리를 진행하게 할 수 있습니다. 정리 중 다시 yield하는 것은 허용된 일반 종료가 아니며 오류가 될 수 있습니다. 실행 중인 generator를 여러 소비자가 동시에 재진입하는 것도 안전한 공유 모델이 아닙니다. 버전별 close 반환값 같은 세부는 해당 Python 계약을 확인합니다.

지연 평가와 비동기 실행의 구분

동기 next 안에서 느린 파일 읽기나 긴 계산을 하면 호출자도 그 시간만큼 막힙니다. asyncio 이벤트 루프에서 동기 generator를 순회한다고 자동으로 다른 스레드가 실행하지 않습니다. async iterator나 제한된 실행 분리가 필요할 수 있습니다.

메모리는 전체 목록보다 줄일 수 있지만 프레임의 지역 객체·현재 원소·소비자가 보관한 결과는 남습니다. 무한 입력을 list로 물질화하거나 tee의 느린 소비자를 방치하면 지연 평가의 장점을 잃습니다.

진단 실습은 첫 next 전 부수 효과, send(None)과 non-None send, child의 return 값, break 뒤 finally 실행을 각각 관찰하는 것입니다. 예상 결과와 다르면 generator 문법 자체보다 소비자가 iterator를 한 번 더 돌렸는지, close를 호출했는지, 파일·DB 자원을 별도 context가 소유하는지를 먼저 확인합니다.

소비 순서·소진 결과·종결 동작 확인

첫 next 전 부수 효과가 없는지, 일부 소비 뒤 list 결과, 소진 후 빈 결과를 검사합니다. child return 값·send·throw·조기 close의 finally 실행을 따로 확인합니다. 동일 데이터를 다시 읽어야 하는 API에는 factory와 원본 snapshot 버전까지 함께 검증합니다. 이 노트의 출력은 Python 규칙에 따른 기대 결과이며 파일·DB 스트림의 실제 종료 검증은 별도입니다.

후속·관련 개념

함께 읽을 문서

전체 기술 지식
선택 연습 · 관련 질문 2개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.