generator는 고급 문법이라기보다 값을 한꺼번에 다 만들지 않고 필요할 때마다 내놓는 방식입니다.
즉 여기서는 yield 문법 자체보다, generator 함수가 iterator를 어떻게 만들고 결과 소비 방식을 어떻게 바꾸는지를 더 중요하게 봅니다.
핵심 정리
def countdown(n):
while n > 0:
yield n
n -= 1
for value in countdown(3):
print(value)문법
기본형은 generator 함수, yield, generator 객체를 함께 보면 됩니다.
def generate():
yield 1
yield 2생성 모델
yield가 들어가면 함수는 generator를 반환한다
yield가 있는 함수는 호출 즉시 끝까지 실행되지 않고 generator 객체를 돌려줍니다.
def countdown(n):
while n > 0:
yield n
n -= 1
it = countdown(3)
print(next(it)) # 3즉 일반 함수가 "결과 하나를 반환하고 끝나는 함수"라면, generator 함수는 "여러 값을 순서대로 내놓는 흐름"을 만듭니다.
generator는 상태를 보존한 채 멈췄다가 다시 이어 간다
return은 함수 종료지만, yield는 현재 지역 변수와 실행 위치를 유지한 채 잠시 멈춥니다.
def demo():
print("A")
yield 1
print("B")
yield 2그래서 큰 파일을 한 줄씩 읽거나, 무한 수열을 만들거나, 단계별 파이프라인을 구성할 때 잘 맞습니다.
list와 generator
리스트와의 차이는 "결과를 언제 다 쓰느냐"다
결과 전체가 바로 필요하고 여러 번 접근해야 하면 리스트가 편합니다. 반대로 한 번 순차적으로 소비하는 흐름이면 generator가 메모리 사용과 파이프라인 구성에서 유리할 수 있습니다.
squares_list = [n * n for n in range(1_000_000)]
squares_gen = (n * n for n in range(1_000_000))generator는 자동으로 더 빠른 도구가 아니라, 지금 전부 만들 필요가 없을 때 맞는 도구입니다.
연결과 재사용
yield from 없이도 먼저 기본 모델을 이해하는 게 중요하다
실전에서 generator는 다른 generator와 연결되기도 하지만, 먼저 중요한 건 iterator 프로토콜 위에서 값을 하나씩 내놓는다는 기본 모델입니다.
이 감각이 잡히면 다음의 itertools나 파일 반복 카드도 훨씬 쉽게 읽힙니다.
한 번 소비되면 다시 처음부터 읽지 못한다
generator는 iterator의 일종이므로 한 번 소비하고 나면 비워집니다. 여러 번 써야 한다면 새 generator를 다시 만들거나 리스트로 물질화해야 합니다.
주의할 점
generator는 한 번 소비하면 다시 처음으로 돌아가지 않습니다. 여러 번 써야 하는 결과라면 list가 더 맞을 수 있습니다.
# ❌ 한 번 소비한 뒤 두 번째는 비어 있음
values = (n * n for n in range(3))
print(list(values))
print(list(values))
# ✅ 다시 써야 하면 리스트로 만든다
values = [n * n for n in range(3)]
print(values)
print(values)참고 링크
2 sources