[Python]generators, lazy evaluation

Jimin_Note·2022년 5월 28일
0

[Python]

목록 보기
25/34

📍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__를 호출할 수 있다.

iterator 예제

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
'''

generator 예제

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에 넘겨준다

📍제너레이터 표현식(generator expression)

  • Lazy evaluation을 위해서 사용(즉, 실행을 지연시킴)
  • 리스트 컴프리헨션 문법과 비슷하지만 대괄호( [ ])가 아닌 괄호를 ( ) 사용

📍제너레이터 함수와 제너레이터 표현식 비교

제너레이터 함수

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명의 학생이 수강신청을 하였다.
개강날에 학생들을 위해 긴장을 풀어주는 따듯한 웰컴티를 미리 준비하려고 하는데 몇몇 학생들로부터 개강일에 참여를 할 수도 있고 못할 수도 있다는 애매모호한 연락을 받았다.
이러한 상황에서 해결방법은

  1. 전부 다 올수도 있다는 전제로 35개의 웰컴티를 준비한다.
    -> 만얀 참여못한 학생들이 있다면 그들을 위해 준비한 웰컴티는 쓰레기가 된다.
  2. 찻잔에 티백만 넣어둔체 준비만 해놓고 학생들이 등원을 하면 그때 한명한명 따듯한 물을 부어준다.
    -> 학생들이 오자마자 바로 마시진못하고 줄을 서서 좀 기다려야 하지만 버리게되는 티는 없게된다.

이 때 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

lazy evaluation장점

  • 안정성
    위 예제처럼 배열의 크기가 3처럼 작은 단위가 아니라 크기가 클 경우를 생각해보면 단위마다 evaluate(평가) 하며 메모리를 할당하는 lazy evaluation을 사용하여 부담을 줄일 수 있다.
  • 성능개선
    느긋하게 계산하여 필요하지 않은 연산을 하지않아 실행을 더 빠르게 할수 있고 복합 수식을 계산할때에도 에러를 피할 수 있다.(불필요한 연산 X )
profile
Hello. I'm jimin:)

0개의 댓글