파이썬에서 보통의 함수는 값을 반환하고 종료 하지만 제너레이터 함수는 값을 반환하기는 하지만 산출(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는 제너레이터 함수에서 값을 반환할 때 사용되며 yield 호출후에 다시 next가 호출될때 까지 현재의 상태에서 머물고 있다가 next 함수가 호출되면 이전의 상태에 이어서 다음 연산을 수행.
print(dir(generator_squares()))를 수행하여 보면,
def generator_squares():
for i in range(3):
yield i ** 2
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__
함수가 둘다 들어있어, __iter__
를 호출한 후에 __next__
함수를 호출하지 않아도 __next__
를 바로 호출할 수 도 있습니다.
📌 이터레이터와 마찬가지로 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 실행 루프에서 연산결과에 따라 호출도 제어할 수 있습니다.
📌 제너레이터 표현식은 list comprehension과 비슷한데 괄호만 []가아니고 ()로 변경면 해주면 Generator표현식이 됩니다.
이번 과제는 다음코드를 실행해보고 분석한 결과를 블로깅하는 과제 입니다. lazy evaluation 이란 무엇인지와 장점 및 리스트 컴프리헨션과의 차이점에 대하여 블로깅 해주세요.
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
'''
generator expression
는 일반적인 반복문과는 연산하는 절차가 다르다. 제너레이터는 값을 반환해야 할 때 제너레이터 안의 연산을 잠시 멈추고 외부로 값을 전달함.
따라서 lazy_return(num) 함수에서 return한 값을 가지고 있으면서 print_iter(iter) 함수를 처리해주는 과정을 반복함.
반면 list comprehension
은 lazy_return(num) 함수를 모두 처리한 후 print_iter(iter) 함수를 실행.
generator expression
는 lazy evaluation, 즉 지연 평가 방식으로 작동. 리소스가 낭비되는 것을 방지하기 위해 lazy evaluation을 사용하게 되면 필요한 값을 그때그때 처리하기 때문에 메모리를 더 효율적으로 사용할 수 있음
list comprehension
연산 방식으로 반복문을 한 번에 처리하면 메모리가 그만큼 값을 저장하고 있으므로 서비스 성능을 저하시킬 수 있다. 따라서 함수를 호출 했을 때 연산을 해두고 결과값을 이용하지 않는 경우 메모리의 낭비가 일어남.