Lazy evaluation

minhyuk_ko·2021년 11월 3일
0

Wecode

목록 보기
7/33

제너레이터 함수란?

함수에 yield를 사용하여 이터레이터를 생성하는 함수이다.
참고(https://dojang.io/mod/page/view.php?id=2412)

이 yield 는 어떻게 동작하는 것일까?

yield에 대한 이해를 높이기 위해 다음과 같이 코드를 작성하였다

def num_generator():
    yield 0 # 0을 외부로 전달
    yield 1 # 1을 외부로 전달
    yield 2 # 2를 외부로 전달
for i in num_generator:
    print(i)
############## 결과값 ##############
0
1
2

yield가 들어간 함수를 자세히 보기위해 아래 코드를 실행해보았다.

>>> g = num_generator()
>>> g
<generator object number_generator at 0x03A190F0>
>>>dir(g)
['__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__라는 요소이다.
제너레이터에서 필수로 하는 요소이고 이것이 어떻게 행동하는지 살펴보자.

g.__next__() # 0
g.__next__() # 1
g.__next__() # 2
g.__next__() 
#Traceback (most recent call last):
#  File "<pyshell#29>", line 1, in <module>
#    g.__next__()
#StopIteration

g.__next__() 가 동작할 때마다 num_generator()에 작성한 값이 하나씩 튀어 나온다. 마지막 동작에선 더 이상의 이터레이터가 없으니 StopIteration이라는 예외를 발생시킨다.

즉, 필요에 따라 값을 반환하는 것이다.

이렇게 동작하는 Lazy evaluation 이라 부른다.

값을 밖에서 전달받는 방법 또한 존재한다.

def num_send():
	num = 0
    while True:
    	num = yield # 값을 외부로부터 받는다.
    	print(num) # 받은 값을 출력한다.
        return yield num * 2 # 받은 값 * 2를 외부로 전달.
gen = num_send()
next(gen) # 제너레이터를 실행 <-- gen.__next__() 와 같은 동작.
print(gen.send(1)) # 1 (받은 값) , 2 (전달한 값) 이 출력된다.

제너레이터 표현식 (comprehension 과 비슷)

x = [i for i in range(1,11) if i % 2 == 0] # comprehension
>>> x
[2,4,6,8,10]
y = (i for i in range(1, 11) if i % 2 == 0) # comprehension과 유사하지만 tuple 처럼 소괄호()로 표현하였다.
>>> y
<generator object <genexpr> at 0x024F02A0> 

Comprehension 과 제너레이터 표현식의 차이점

Comprehension은 생성과 동시에 모든 값들이 배열 속에 담겨있지만
제너레이터 표현식의 경우 필요에 의해 값을 생성한다.

메모리 활용에 있어서 Comprehension 보다 제너레이터 표현식이 유리하다 볼 수 있다.

Assignment

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) # time 모듈
    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

Comprehension의 함수 실행순서

print("comprehension_list=")
lazy_return([1,2,3])
print_iter([1,2,3])

제너레이터 표현식의 함수 실행순서

print("generator_exp=")
lazy_return(1)
print_iter(1)
lazy_return(2)
print_iter(2)
lazy_return(3)
print_iter(3)

결론 및 개인적인 생각

comprehension : 함수를 직렬적으로 실행한다.

제너레이터 표현식 : 함수를 병렬적으로 실행한다.

profile
BE Developer

0개의 댓글