2.31. 이터레이터와 제너레이터¶
for 루프는 보기보다 더 많은 일을 해 왔습니다. 이 페이지에서는 그것이 동작하는 기반인 이터레이터 프로토콜(iterator protocol), 그리고 직접 이터레이터를 만들 수 있게 해 주는 yield 키워드를 다룹니다.
2.31.1. 이터레이터 프로토콜¶
반복할 수 있는 모든 객체는 두 가지 메서드를 구현합니다:
__iter__()– 객체의 항목들에 대한 이터레이터(iterator) 를 반환합니다.__next__()– 이터레이터에서 다음 항목을 반환하거나, 더 이상 없으면StopIteration을 발생시킵니다.
iter() 내장 함수는 __iter__ 를 호출하고, next() 는 __next__ 를 호출합니다. 리스트를 직접 하나씩 따라가 봅시다:
it = iter([10, 20, 30])
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
print(next(it)) # raises StopIteration
for 는 “__iter__ 를 한 번 호출한 다음, StopIteration 이 날 때까지 __next__ 를 반복 호출하라”의 문법적 설탕입니다.¶
for x in items: 가 실제로 하는 일:
_it = iter(items)
while True:
try:
x = next(_it)
except StopIteration:
break
# ... loop body ...
모든 리스트, 튜플, 문자열, 딕셔너리, 집합, 파일 객체, 제너레이터는 이미 __iter__ 와 __next__ 를 구현하고 있습니다 – 그래서 이들 모두가 for 와 함께 동작하는 것입니다.
2.31.2. yield와 제너레이터 함수¶
yield 문을 포함하는 함수는 제너레이터 함수입니다. 이를 호출해도 본문이 실행되지 않으며, 본문을 한 번에 하나의 yield씩 실행하는 제너레이터 객체(반복자)를 반환합니다:
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
for value in count_up_to(3):
print(value)
출력:
0
1
2
next() 를 호출할 때마다 함수는 다음 yield 까지 재개되어 그 값을 호출자에게 넘기고 거기서 일시 중단합니다. 지역 상태(이 경우 i)는 재개 사이에 보존됩니다.
next() 는 다음 yield 까지 본문을 실행하고, 값을 돌려준 뒤 일시 중단합니다. 지역 상태는 중단을 거쳐도 살아남습니다.¶
제너레이터는 시퀀스를 지연 방식으로 생성하는 가장 쉬운 방법입니다 – 리스트가 만들어지지 않고, 항목은 소비자가 요청할 때만 계산되며, 원한다면 함수가 항목을 영원히 내보낼 수도 있습니다.
2.31.3. 지연 파이프라인¶
제너레이터는 잘 조합됩니다. 한 제너레이터의 출력이 다른 제너레이터의 입력이 될 수 있습니다:
def numbers():
i = 0
while True:
yield i
i += 1
def squares(source):
for x in source:
yield x * x
pipeline = squares(numbers())
for v in pipeline:
if v > 100:
break
print(v)
값은 파이프라인을 한 번에 하나씩 흘러갑니다 – 중간 리스트도 없고, numbers 에 내장된 상한도 없으며, 소비자(for v in pipeline)가 언제 멈출지 결정합니다.
소비자 쪽의 각 next() 는 체인을 따라 한 번의 끌어오기를 일으킵니다; 값은 무언가가 요청할 때만 존재합니다.¶
2.31.3.1. yield from¶
다른 이터러블에서 항목을 끌어와 하나씩 yield 하는 루프는 충분히 흔해서 Python이 단축 표현을 제공합니다. yield from iter 식은 마치 제너레이터에 for x in iter: yield x 루프가 인라인으로 들어있는 것처럼, 이터러블이 생성하는 각 값을 순서대로 내보냅니다:
def chain(*sources):
for source in sources:
yield from source
for v in chain([1, 2, 3], (4, 5), "abc"):
print(v)
출력:
1
2
3
4
5
a
b
c
yield from 은 명시적인 for 루프와 정확히 동등하며, 단지 더 짧을 뿐입니다. 또한 내부 이터러블의 StopIteration 을 외부 제너레이터로 깔끔하게 전파합니다 – 여러 제너레이터를 끝에서 끝으로 연결할 때 유용합니다.
2.31.3.2. yield 가 끝났을 때¶
제너레이터 함수의 끝을 넘어가거나(또는 명시적인 return 에 도달하면) 자동으로 StopIteration 이 발생합니다. 직접 발생시킬 필요는 없으며, 둘러싼 for 루프가 이를 감지하고 종료합니다.
생성하는 코드가 자연스럽게 몇 개의 yield 지점을 가진 루프로 작성될 때는 제너레이터를 사용하고, 전체 시퀀스가 정말로 메모리에 필요할 때는 단순한 리스트 컴프리헨션을 사용하세요.