제너레이터 함수 : 값을 반환하기는 하지만 산출(yield)한다.
그리고 제너레이터는 '이터레이터를 생성해주는 함수'라고도 볼 수 있다.
다음 코드는 함수안에서 yield를 사용하여 리스트의 제곱을 산출하는 함수가 있고,
이 함수를 print문으로 확인해보면 generator object 임을 확인할 수 있다.
def generator_squares(): for i in range(3): yield i ** 2 print("gen object=", end=""), print(generator_squares()) gen object=<generator object generator_squares at 0x10f3b0150>
우리가 주목할 키워드는 yield 이다.
제너레이터를 dir로 함수 종류를 확인해보면 이터레이터와는 다르게 __iter__ 와 __next__ 함수가 둘다 들어있는 것을 확인할 수 있다.
print("dir gen =", end=""), print(dir(generator_squares()))
dir gen =['__class__', '__del__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__',
'__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
그래서 이터레이터에서 처럼 __iter__를 호출한 후에 __next__함수를 호출하지 않아도 __next__를 바로 호출할 수 도 있다.
gen = generator_squares() print(gen.__next__()) print(gen.__next__()) print(gen.__next__()) print(gen.__next__()) 출력결과 0 1 4 Traceback (most recent call last): File "generator.py", line 14, in <module> print(gen.__next__()) StopIteration
이터레이터처럼 __next__ 함수로 값을 꺼내올 수 있고
for문이 끝나게 되면 StopIteration이 발생한다.
그리고 제너레이터 함수는 실행중에 send 함수를 통해서 값을 전달할 수도 있다.
def generator_send(): received_value = 0 while True: received_value = yield print("received_value = ",end=""), print(received_value) yield received_value * 2 gen = generator_send() next(gen) print(gen.send(2)) next(gen) print(gen.send(3)) 출력결과 received_value = 2 4 received_value = 3 6
generator_send 함수는 yield로 send를 통해서 받은 값을 received_value에 할당하고 그 값의 2배 수를 리턴받고 있다.
제너레이터에서는 이처럼 yield를 이용해서 제너레이터 함수 실행중 값을 전달한다.
응용하면 제너레이터 함수를 사용해서 main 실행 루프에서 연산결과에 따라 호출도 제어할 수 있다.
제너레이터 함수외에도 제너레이터 표현식(generator expression)이 있다.
제너레이터 표현식은 실행 지연(Lazy evaluation)을 위해서 사용될 수 있다.
제너레이터 표현식의 문법은 리스트 컴프리헨션 문법과 비슷하지만 대괄호( [ ])가 아닌 괄호를 ( ) 사용하여 만든다.
예제)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() )
output > 1,4,9
이번 과제는 다음코드를 실행해보고 분석한 결과를 블로깅하는 과제 입니다. 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)
output >
위의 코드를 보면 comprehension_list는 [] 대괄호로 쌓여 있고, generator_exp는 ()중괄호로 쌓여있다.
comprehension_list는 리스트기 때문에 위 코드처럼 리스트 컴프리헨션(lazy_return(i))을 먼저 진행해주고나서 아래 print를 출력해준다. 즉, 리스트 컴프리헨션은 lazy_return(num) 함수를 모두 처리한 후 print_iter(iter) 함수를 실행한다.
그러나 generator_exp의 경우 일반적인 반복문과는 연산하는 절차가 다르다.
제너레이터는 값을 반환할 때 연산을 잠시 멈추고 외부로 값을 보낸다.
따라서 lazy_return(num) 함수에서 return한 값을 갖고
print_iter(iter) 함수를 처리해준다.
lazy evaluation이란?
Lazy Evaluation은 어떤 값이 실제로 쓰일 때 까지 그 값의 계산을 뒤로 미루는 동작 방식
lazy evaluation의 장점
리스트의 경우 내부 데이터가 크면 한정된 용량의 메모리는 감당할 수 없다.
그러나 제너레이터 표현식(generator expression)을 통한 Lazy evaluaiotn은 데이터를
메모리에 로딩하지 않고, 필요할 때 로딩, 평가를함으로 메모리를 절약 할 수 있다.
리스트 컴프리헨션과의 차이점
[참고: https://velog.io/@junnoli/Iterator-Generator]