📍generators
- 값을 반환하고 종료하는 보통의 함수와 다르게 값을 반환한후 산출(yield)하는 함수
- iterator를 생성해주는 함수
일반 함수
def generator_squares():
for i in range(3):
return i
print(generator_squares()) #0
generator
def generator_squares():
for i in range(3):
yield i
print(generator_squares())
#<generator object generator_squares at 0x7fa2d0567970>
일반 함수에서 쓰는 return 자리에 있는 yield
return처럼 값을 반환 후 next가 호출될때까지 현재의 상태에 머물고 있다가 next함수가 호출되면 이전의 상태에 이어서 다음 연산을 수행
__iter__
을 호출한 후에 __next__
를 호출해야되는 iterator과는 다르게 바로 __next__
를 호출할 수 있다.
L = [1, 2, 3]
iterator_L=iter(L) #iter함수 호출 후
print(iterator_L.__next__()) #next함수 호출
print(iterator_L.__next__())
print(iterator_L.__next__())
print(iterator_L.__next__())
'''
1
2
3
Traceback (most recent call last):
File "main.py", line 7, in <module>
print(iterator_L.__next__())
StopIteration
'''
def generator_squares():
for i in range(3):
yield i
gen = generator_squares()
print(next(gen)) #iter함수없이 바로 next 호출
print(next(gen))
print(next(gen))
print(next(gen))
'''
0
1
2
Traceback (most recent call last):
File "main.py", line 9, in <module>
print(next(gen))
StopIteration
'''
📍send함수
실행 중 send함수를 통해 값을 전달할 수 있다
def generator_send():
received_value = 0
while True:
received_value = yield #yield로 send를 통해 받은 값을 received_value에 할당
print("received_value = ",end=""),print(received_value) #send를 통해 받은 값 출력
yield received_value * 2 #그 값에 2 곱한 값을 send함수를 통해 출력
gen = generator_send() #함수생성
next(gen) #send로 함수에 인자 값을 전달
print(gen.send(2)) #end로 받아온 값을 received_value에 넘겨준다
next(gen)
print(gen.send(3))
'''
received_value = 2
4
received_value = 3
6
'''
gen이라는 함수를 생성 (generator_send 함수에는 입력값이 없다)
그리고 첫 element를 접근하기 위해 next(gen)으로 간 후
여기서 send로 함수에 인자 값을 전달한다.
yield가 send로 받아온 값을 received_value에 넘겨준다
📍제너레이터 함수와 제너레이터 표현식 비교
제너레이터 함수
L = [ 1,2,3]
def generate_square_from_list():
for x in L:
yield x*x
gen = generate_square_from_list()
print(next(gen))
print(next(gen))
print(next(gen))
'''
1
4
9
'''
제너레이터 표현식
L = [ 1,2,3]
def generate_square_from_list():
result = ( x*x for x in L ) #generator expression
return result
def print_iter(iter):
for element in iter:
print(element)
print_iter( generate_square_from_list() )
'''
1
4
9
'''
제너레이터 함수 예제와 비교해보면
제너레이터 함수에서 print(next(gen))을 반복하여 나타낸 부분을 제너레이터 표현식에서는 print_iter함수는 를 이용하여 for loop로 표현한 것을 볼 수 있다.
generate_square_from_list함수는 제너레이터 표현식을 이용하여 result라는 제너레이터 객체는 만들었다.
L이 1일 때,
generate_square_from_list()
result = ( x*x for x in L )
👉result = (1*1) == (1) #generator expression
print_iter(iter)
for element in iter:
print(element) #1
📍Lazy Evaluation
계산의 결과값이 필요할때 까지 계산을 늦추는방법
필요없는 계산은 하지않고 필요해지며 그때 실행하는 방식
ex)
위코드 34기에 35명의 학생이 수강신청을 하였다.
개강날에 학생들을 위해 긴장을 풀어주는 따듯한 웰컴티를 미리 준비하려고 하는데 몇몇 학생들로부터 개강일에 참여를 할 수도 있고 못할 수도 있다는 애매모호한 연락을 받았다.
이러한 상황에서 해결방법은
이 때 2번처러 해결하는 방식을 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
📍위 함수를 이용해서 리스트 컴프리헨션과 lazy evaluation의 차이를 알아보자
리스트 컴프리헨션
print("comprehension_list=")
comprehension_list = [ lazy_return(i) for i in L ] #대괄호
print_iter(comprehension_list)
'''
comprehension_list=
sleep 1s
sleep 1s
sleep 1s
1
2
3
'''
<결과>
sleep 1s 가 1초마다 연속으로 출력=>lazy_return함수를 미리 3번연속실행하여
값을 미리 받아 놓은 후 print_iter함수로 1,2,3 이 동시에 출력된다
즉, 웰컴티를 원래 오기로했던 학생수만큼 미리 만들어두는 방식이다.
Lazy Evaluation
print("generator_exp=")
generator_exp = ( lazy_return(i) for i in L ) #소괄호
print_iter(generator_exp)
'''
generator_exp=
sleep 1s
1
sleep 1s
2
sleep 1s
3
'''
<결과>
sleep 1s
1
-출력 후 1초뒤
sleep 1s
2
-출력 후 1초뒤
sleep 1s
3
generator_exp 값을 불러온 그 순간에만 실행된다
즉, 학생들이 들어오면 한명한명에서 따듯한 물을 부어주는 방식
💁♂️출력결과를 비교하면,
comprehesion_list가 처음에 1초씩 3번출력으로 3초가 걸리는 것은 사이의 list comprehension식에 해당하는 메모리에 배열의 크기만큼 미리 할당하기 때문
Generator Expression 형태의 함수인 generator_exp는 컴프리헨션처럼 공간은 생성하지만, 배열의 구체적인 형태를 갖고 있지 않는다.
즉, generator expression은 지정한 규칙대로 값을 반환할 규칙과 현재 어디까지 반환했는지 등을 관리할 여러 상태값을 담고 있.
generator expression을 사용한 것 👉 lazy evaluation(loading)
list comprehension과 같이 생성과 동시에 메모리에 적재하는 형태 👉 eager loading