Python에서 generator는 iterator를 쉽게 만들 수 있는 방법입니다. 함수 안에서 return 대신 yield를 사용하면 함수는 값을 하나 반환하고 종료되는 것이 아니라, 현재 실행 상태를 저장한 채 멈춥니다. 다음 next() 호출 때 그 지점부터 다시 실행됩니다.
장점은 모든 데이터를 메모리에 올리지 않고 필요한 만큼만 생성할 수 있다는 점입니다. 큰 파일 처리, stream 처리, 무한 sequence 표현에 유리합니다.
꼬리질문
조금 더 깊게 물어본다면
답변 뒤에 이어질 수 있는 질문들을 하나씩 열어볼 수 있어요.
yield와 return의 차이는?
return은 함수를 종료하고 값을 반환합니다. yield는 값을 하나 내보낸 뒤 함수 상태를 보존하고 다음 next() 호출 때 그 지점부터 이어서 실행합니다.
yield가 실행을 일시정지하는 원리는?
CPython은 generator 함수가 호출되면 함수 바디를 즉시 실행하지 않고 generator 객체를 반환합니다. next()를 호출하면 함수의 프레임 객체에서 실행이 시작되고, yield를 만나면 지역 변수와 명령어 포인터를 프레임에 보존한 채 실행을 중단합니다. 다음 next() 호출 시 저장된 프레임에서 다시 재개합니다.
Generator는 이터레이터와 어떻게 다른가요?
이터레이터는 __iter__와 __next__를 구현한 모든 객체입니다. Generator는 yield를 사용한 함수로 만들어지며 이터레이터 프로토콜을 자동으로 구현합니다. 즉 generator는 이터레이터의 특수한 형태입니다. 반대로 이터레이터가 모두 generator인 것은 아닙니다.
Generator와 list의 차이는?
list는 모든 값을 메모리에 저장하지만 generator는 요청될 때 값을 하나씩 계산합니다. 대신 한 번 소비하면 다시 처음부터 반복할 수 없습니다.
yield가 좋은 이유는?
lazy evaluation이 가능해 메모리를 아끼고, 큰 데이터나 무한 sequence를 다루기 쉽습니다.
Generator expression은?
list comprehension과 비슷하지만 대괄호 대신 괄호를 사용합니다. 모든 값을 즉시 만들지 않고 필요할 때 하나씩 생성합니다.
Python의 async/await와 generator는 어떤 관계인가요?
Python의 async/await는 generator를 기반으로 발전했습니다. Python 3.4까지는 yield from을 이용한 coroutine 패턴을 사용했고, 3.5에서 async/await 키워드가 추가되었습니다. await는 내부적으로 generator의 yield처럼 실행을 일시 중단하고 이벤트 루프에 제어권을 넘깁니다.
무한 시퀀스를 generator로 만들 수 있나요?
만들 수 있습니다. while True 루프 안에서 yield를 사용하면 next()를 호출할 때마다 다음 값을 생성하는 무한 시퀀스를 표현할 수 있습니다. list로는 무한 시퀀스를 메모리에 올릴 수 없지만 generator는 값을 하나씩 생성하기 때문에 가능합니다.
부가 설명
Generator는 iterator protocol을 따릅니다. 즉 __iter__와 __next__를 통해 반복됩니다.
내부적으로는 frame과 instruction pointer, local variable 상태를 보존하기 때문에 다음 호출에서 이어서 실행할 수 있습니다.
한 줄 정리
Generator는 값을 한 번에 모두 만들지 않고 필요할 때 하나씩 생성하는 iterator이고, yield는 함수 실행 상태를 멈췄다가 재개하게 합니다.