제너레이터
- iterator 객체의 한 종류
- 제너레이터를 전달하면서 next 함수를 호출하면 값을 하나씩 얻을 수 있습니다.
- 제너레이터를 만드는 방법은 두 가지가 있습니다.
1. 제너레이터 함수 : 제너레이터를 만들기 위한 함수 정의
def gen_num():
print('first number')
yield 1
print('second number')
yield 2
gen = gen_num()
type(gen)
next(gen)
next(gen)
- 마지막까지 실행했음에도 다시 next 함수를 호출하면 이번에는 StopIteration 예외가 발생합니다. 그 이유는 제너레이터 객체 역시 iterator객체이기 때문입니다.
- 함수 호출 이후에 그 실행의 흐름을 next 함수가 호출될 때까지 미루는 특성을 가리켜 lazy evalutaion이라고 합니다.
제네레이터의 장점
- 제너레이터를 사용하면 그렇지 않은 경우보다 메모리를 절약할 수 있습니다.
- 제너레이터를 사용하는 경우 리스트의 길이에 상관없이 메모리 공간의 크기가 동일한데, 그 이유는 제너레이터 객체는 반환할 값들을 미리 만들어서 저장해 두지 않기 때문입니다.
yield from
def get_nums():
ns = [0, 1, 0, 1, 0, 1]
for i in ns:
yield i
g = get_nums()
next(g)
next(g)
- 여기에서 for 문을 이용하여 yield를 통해 ns의 값을 하나씩 전달하였습니다. 하지만 이것보다 쉽게 코드를 작성하는 방법이 있습니다. 바로 yield from을 사용하는 것입니다.
def get_nums():
ns = [0, 1, 0, 1, 0, 1]
yield from ns
g = get_nums()
next(g)
next(g)
2. 제너레이터 표현식 : 제너레이터를 만들기 위한 식
- 제네레이터 표현식은 문법 구성이 리스트 컴프리헨션과 거의 비슷합니다.
- 리스트 컴프리헨션으로 만들기
def show_all(s):
for i in s:
print(i, end = ' ')
st = [2 * i for i in range(1, 10)]
show_all(st)
def show_all(s):
for i in s:
print(i, end = ' ')
g = (2 * i for i in range(1, 10))
show_all(st)
- 제너레이터 표현식은 [...]에서 (...)으로 바뀌었을 뿐 그 안을 채우는 방법은 리스트 컴프리헨션과 똑같습니다.
정리
- 생성되는 값들을 순서대로 하나씩 가져다 쓰면 되는 상황에서는 이렇듯 제너레이터를 기반으로 코드를 작성하는 것이 합리적입니다. (메모리를 절약할 수 있기 때문)
- map과 filter도 제너레이터 함수이고, iterator 객체이자 제너레이터 객체입니다.
- 제너레이터 표현식은 제너레이터 함수보다 간결합니다. 하지만 표현해야할 식이 복잡하다면 그 장점을 잃게 될 수도 있습니다. 따라서 상황별로 제너레이터를 만드는 방법을 적절히 이용해야 합니다.