제너레이터(generator)
제너레이터는 이터레이터를 생성해주는 함수다. 함수 안에서 yield라는 키워드를 사용하면 함수는 제너레이터가 되며, yield에는 값(변수)을 지정한다.
yield를 사용해서 제너레이터를 만들고, 숫자를 출력하는 for 반복문을 만들어보자.
def numbers():
for num in range(1, 11):
if num % 2 == 1:
yield num
for i in numbers():
print(i, end = " ")# 출력값
1 3 5 7 9제너레이터 함수의 작동 방식
일반적인 함수는 함수를 모두 실행한 뒤에 외부로 값을 반환하지만, 제너레이터 함수는 yield가 나오는 순간 값을 반환한 뒤 잠시 연산을 멈추고 함수 바깥의 코드가 실행되도록 양보하여 값을 가져가게 한다. 그리고 다시 제너레이터 안의 코드 중 yield 이후부터 이어서 실행한다.
즉 위 코드를 다시 뜯어보면,
for문에서numbers()제너레이터 함수 호출numbers()함수 안 첫 번째 반복문 제1턴 실행yield키워드 만남- 값 반환
numbers()함수 바깥으로 나가 외부for문에 값 전달하여print실행- 다시
numbers()함수로 돌아와서 제2턴 실행 - 반복
의 과정을 거쳐 결과값을 반환한다.
제너레이터 표현식
제너레이터 표현식은 이터레이터를 반환한다. 제너레이터 함수를 좀 더 쉽게 사용할 수 있도록 해준다. 리스트 컴프리헨션과 구성이 비슷하나 리스트 컴프리헨션은 대괄호[]를 쓰고 제너레이터 표현식은 소괄호()를 쓴다.
(표현식 for 원소 in 반복 가능한 객체)
(표현식 for 원소 in 반복 가능한 객체 if 조건문)generator_exp = (i for i in range(1,11))
print(generator_exp)# 출력값
<generator object <genexpr> at 0x7fd638047c50>generator_exp가 제너레이터 객체임을 확인할 수 있다.
Q1. 다음코드는 generator expression을 사용해서 제곱연산을 하는 예제 입니다. 실행해보고 결과를 확인해보세요.
L = [ 1,2,3]
def generate_square_from_list():
result = ( x*x for x in L )
print(result)
return result
def print_iter(iter):
for element in iter:
print(element)
print_iter( generate_square_from_list() )A1.
# 출력값
<generator object generate_square_from_list.<locals>.<genexpr> at 0x7fd35820fbd0>
1
4
9generate_square_from_list() 함수의 print(result) 코드와 return result코드는 한 번 실행한 뒤 종료되었다. 따라서 print(result)의 값인 <generator object generate_square_from_list.<locals>.<genexpr> at 0x7fd35820fbd0>가 한 번 출력된 것을 확인할 수 있다.
print와 return을 실행한 뒤, 제너레이터가 반환한 값 1이 print_iter(iter)로 전달되어 1이 화면에 출력된다.
이후로는 제너레이터 표현식인 result와 print_iter(iter) 함수를 오가며 진행된다. 4와 9는 이때 출력된다.
Q2. 이번 과제는 다음코드를 실행해보고 분석한 결과를 블로깅하는 과제 입니다. lazy evaluation 이란 무엇인지와 장점 및 리스트 컴프리헨션과의 차이점에 대하여 블로깅 해주세요.
L = [1, 2, 3]
import time
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)A2.
주어진 코드를 실행하면 아래와 같이 실행된다.
# 출력값
comprehension_list=
sleep 1s
sleep 1s
sleep 1s
1
2
3
generator_exp=
sleep 1s
1
sleep 1s
2
sleep 1s
3print("comprehension_list=")실행comprehension_list=출력comprehension_list = [ lazy_return(i) for i in L ]실행- 첫 번째 값
1가지고comprehension_list안lazy_return(num)실행 print("sleep 1s")실행sleep 1s출력time.sleep(1)실행, 1초 기다림return num실행해서num값1반환- 다음 값
2가지고comprehension_list안lazy_return(i)실행 - 5~8번 반복
return num실행해서num값2반환- 다음 값
3가지고comprehension_list안lazy_return(i)실행 - 5~8번 반복
return num실행해서num값3반환comprehension_list = [1, 2, 3]완성print_iter(comprehension_list)실행comprehension_list리스트 안에 있는 요소1,2,3출력
여기까지가
comprehension_list=
sleep 1s
sleep 1s
sleep 1s
1
2
3의 출력 과정이다. 이어서 계속 보자.
print("generator_exp=")실행generator_exp=출력generator_exp = ( lazy_return(i) for i in L )실행- 첫 번째 값
1가지고lazy_return(num)실행 print("sleep 1s")실행sleep 1s출력time.sleep(1)실행, 1초 기다림return num실행해서num값1반환generator_exp바깥으로 나감print_iter(generator_exp)실행- 요소
1출력 generator_exp = ( lazy_return(i) for i in L )실행- 다음 값
2가지고lazy_return(num)실행 print("sleep 1s")실행- 22~24번 반복
return num실행해서num값2반환- 26~27번 반복
- 요소
2출력 generator_exp = ( lazy_return(i) for i in L )실행- 다음 값
3가지고lazy_return(num)실행 - 22~24번 반복
return num실행해서num값3반환- 26~27번 반복
- 요소
3출력
여기까지가
generator_exp=
sleep 1s
1
sleep 1s
2
sleep 1s
3의 출력 과정이다.
제너레이터는 일반적인 반복문과는 연산하는 절차가 다르다. 앞서 언급했듯 제너레이터는 값을 반환해야 할 때 제너레이터 안의 연산을 잠시 멈추고 외부로 값을 전달한다. 따라서 lazy_return(num) 함수에서 return한 값을 가지고 있으면서 print_iter(iter) 함수를 처리해주는 과정을 반복한다.
반면 리스트 컴프리헨션은 lazy_return(num) 함수를 모두 처리한 후 print_iter(iter) 함수를 실행한다.
제너레이터는 lazy evaluation, 즉 지연 평가 방식으로 작동한다. lazy_return(num) 함수가 훨씬 더 무거운 함수라고 가정해보자. 이때 리스트 컴프리헨션 연산 방식으로 반복문을 한 번에 처리하면 메모리가 그만큼 값을 저장하고 있으므로 서비스 성능을 저하시킬 수 있다. 제너레이터는 필요한 값을 그때그때 처리하기 때문에 메모리를 더 효율적으로 사용할 수 있다.