generator에 대해서 정리를 합니다.

generator의 정의는 generator iterator를 반환하는 함수입니다.
generator iterator의 특징은
- generator iterator는 일반 iterator와 유사하지만 generator에서 yield 구문을 통하여 동작
- generator iterator는 iterator를 사용하는 모든 환경을 대체 가능
generator의 장점은
- iterator에 비해 메모리를 적게 사용
- lazy evaluation 으로 동작
generator에 사용되는 yield에 대한 정리입니다.
Yield
generator가 yield를 만나면 그 상태를 보존하고 있다가, 다시 generator가 호출되면 이어서 수행
yield는 generator에서 값을 반환하거나 값을 입력받는 기능을 수행
다음은 yield 사용 code입니다
3 line의 gen1()은 호출될 때마다 각 yield가 호출하는 값을 return 하게 됩니다.
8 line의 gen2()은 호출될 때마다 yield v 의 값을 return 하게 됩니다.
또한, 22 line의 generator iterator인 g2는 send를 통하여 값을 전달할 수 있는데,
12 line의 code를 보면 send로 gen2() 호출 시, yield에 값이 먼저 전달이 되고 좌측 v에 값을 저장한 뒤, yield는 우측의 v값을 return 하게 됩니다.
next로 gen2() 호출 시, yield는 우측의 v 값을 return 하고 좌측에 None 값을 저장하게 됩니다.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/usr/bin/python3 def gen1():     yield 1     yield 2     yield 3 def gen2():     v = 1     while True:         v = yield v         print("-----------")         print("v : ", v) def main():     print("test1")     for g in gen1():         print(g)      print("test2")     g2 = gen2()     print(next(g2))        print(g2.send(3))        print(g2.send(5))        print(next(g2))    if __name__ == "__main__":     main() | cs | 
출력 결과입니다.
gen1()의 generator iterator를 for문을 이용하여 출력한 결과 1,2,3 이 출력됩니다.
gen2()의 generator iterator에서 next, send, send, next를 이용한 결과 1,3,5,None이 출력됩니다.

Iterator와 Generator iterator와의 비교
다음은 sample code 입니다.
code를 실행하면 73, 74 line에서 test1() 과 test2()를 실행시킵니다.
test1()은 gen1()으로부터 얻은 list의 iterator를 test 합니다.
test2()은 gen2()로부터 얻은 generator iterator를 test 합니다.
10번의 반복 수행 후 평균 값을 얻기 위해 test_more라는 decorator를 만들고 이용하였습니다.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | #!/usr/bin/python3 import time def gen1(count):     g_list = []     for i in range(count):         g_list.append(i)          return g_list def gen2(count):     for i in range(count):         yield i def test_more(f):     def inner(*args, **kwargs):         count = 10         init_t, process_t, total = 0, 0, 0         for i in range(count):             init_t_, process_t_, total = f(*args, **kwargs)             init_t += init_t_             process_t += process_t_         print("%s average - init time : %s, process time : %s, total : %s\n" %              (f.__name__, init_t/count, process_t/count, total/count))     return inner @test_more def test1():     t1 = time.clock()     g_ret = gen1(1000000)     t2 = time.clock()     init_t = t2 - t1     t1 = time.clock()     total = 0     for v in g_ret:         total += v     t2 = time.clock()     process_t = t2 - t1     return init_t, process_t, total @test_more def test2():     t1 = time.clock()     g_ret = gen2(1000000)     t2 = time.clock()     init_t = t2 - t1     t1 = time.clock()     total = 0     for v in g_ret:         total += v     t2 = time.clock()     process_t = t2 - t1     return init_t, process_t, total if __name__ == "__main__":     test1()     test2() | cs | 
시험은 총 1000000개의 integer형 data를 취급하는 것으로 했으며 init, process time 및 해당 data를 모두 더한 값이 동일한지 살펴 보았습니다.
1. 최초 iterator를 얻어 오는 부분은 test1() 이 오래 걸립니다.
※ test1에서 iterator를 가져오기 위해 전체 data를 memory에 올리므로 시간이 좀 더 걸립니다.
※ test2에서 generator iterator는 lazy evaluation을 하므로, 시간이 거의 소요되지 않습니다.
2. data를 for문을 통해 처리하는 부분은 test2가 더 오래 걸립니다.
※ generator iterator의 경우 lazy evaluation 을 하므로 over head가 있는 것 같습니다.
3. 결과적으로 init + process time을 고려하면 generator를 사용한 test2가 좀 더 빠른 모습을 보여줍니다.

'프로그래밍 > PYTHON' 카테고리의 다른 글
| python - Equality, Identity (0) | 2021.02.12 | 
|---|---|
| python - Comprehension, Generator expression (0) | 2021.02.11 | 
| [환경설정] python - Linux anaconda (0) | 2021.02.10 | 
| python - Iterable, Iterator (0) | 2021.02.03 | 
| python - Decorator (4) | 2021.01.22 |