제너레이터는 Iterator 객체의 한 종류이다. 때문에 제너레이터를 통해서 next 함수를 호출하면 값을 하나씩 얻을 수 있다.
제너레이터를 마드는 방법에는 두가지가 있다. 제너레이터 함수와 제너레이터 표현식이 그것이다.
아래는 함수를 기반으로 만든 제너레이터 예시다.
def generator_num():
print("first")
yield 1
print("second")
yield 2
print("third")
yield 3
# 제너레이터 함수 정의
# yield가 하날라도 들어가면 제너레이터가 된다
g = generator_num() # 제너레이터 객체 생성
next(g) # next 함수 호출
마지막 yield가 실행되고 다시 next 함수를 호출하면 StopIteration 예외가 발생한다. 제너레이터 객체 역시 Iterator 객체이기 때문이다. 참고로 함수 호출 이후에 그 실행의 흐름을 next 함수가 호출 될 때까지 미루는 특성을 가리켜 lazy evaluation 이라 한다.
이제 제너레이터의 장점에 대해서 한번 알아보겠다. 아래는 그 예시다.
def gen_pows(s):
for i in s:
yield i ** 2
st = gen_pows([1,2,3,4,5])
for i in st:
print(i, end=" ")
위 예시는 제너레이터를 기반으로 작성되었다. 위 예시를 제너레이터 기반이 아닌 형태로 작성할 수도 있을 것이다. 하지만 그렇게 된다면 인자로 주어지는 리스트 크기에 따라서 메모리 공간을 차지하게 될 것이다. 하지만 제너레이터는 리스트의 길이에 상관없이 사용하는 메모리 공간의 크리가 같다. 이유는 제너레이터 객체는 반환할 값들을 미리 만들어서 저장하지 않기 때문이다.
이제 제너레이터 객체를 생성하는 방법 중 다른 하나인 제너레이터 표현식에 대해서 알아보겠다.
제너레이터 표현식은 리스트 컴프리헨션과 비슷하다. 리스트 컴프리헨션은 [ ... ]
을 사용하지만 제너레이터 표현식은 ( ... )
을 사용한다.
import time
L = [1,2,3]
def print_iter(iter):
for element in iter:
print(element)
def lazy_return(num):
print("sleep 1s")
time.sleep(1)
return num
print("comprehension_list=")
comprehension_list = [ lazy_return(i) for i in L ]
print_iter(comprehension_list)
print("generator_exp=")
generator_exp = ( lazy_return(i) for i in L )
print_iter(generator_exp)
comprehension_list=
sleep 1s
sleep 1s
sleep 1s
1
2
3
generator_exp=
sleep 1s
1
sleep 1s
2
sleep 1s
3
위 결과는 무엇을 의미하는가? 리스트 컴프리헨션은 리스트 원소들을 미리 결과를 보고 저장하는 반면에 제너레이터의 경우 원소들을 호출 될 때 생성하는 것을 의미한다.
이것이 제너레이터가 갖는 lazy evaluation 특성이라고 할 수 있다.
1. 윤성우 열혈 파이썬 중급편