Python의 Generator 활용하기

게시일 : 2019년 04월 14일    
# Python # Generator

Python에서 Generator 사용법을 알아본다.

Iterable VS Iterator

Iterable object

리스트, Set, Dictionary와 같은 컬렉션이나 문자열과 같은 문자 Sequence를 말한다. ( for 문과 같이 하나씩 데이터를 처리할 수 있다. )

기본적으로, Iterable은 Iterator가 아니다.

Iterator object

next() 함수로 데이터를 순차적으로 호출가능한 object

Iterable object를 Iterator object로 활용

my_list = [1,2]
my_iter = iter(my_list) # iter 함수로 iterator로 선언

next(my_iter)  # 1
my_iter.__next__() # 2  python3에서는 __next__()도 가능

next()로 가져올 데이터가 없을 경우, StopIteration exception을 발생시킨다.

next(my_iter)
------------------------------------------------------------------

<ipython-input-34-94a4229f6542> in <module>
----> 1 next(my_iter)

StopIteration: 
next(my_iter, "No more data")  # StopIteration exception 예외 처리
'No more data'
# for 문을 활용하면 exception없이 마지막 데이터 처리 후 종료된다.
my_iter = iter([1,2,3])
for i in my_iter:
    print(i) # i == next(my_iter)
1
2
3

참고 : for in [list] 형태는 iter(list)를 내부에서 호출한다.

Generator - iterator의 특수한 형태

next()를 호출하면 yield를 찾을 때까지 코드를 수행한다. yield를 만날 경우, 해당 함수는 값을 전달하고 그 라인에서 정지된다.

이후, 해당 함수는 종료되는 것이 아니라 그 상태로 유지된다. 다음 yield가 없을 때 종료된다.

함수의 local 변수 등 함수 내부에서 사용된 데이터들은 메모리에 그대로 유지된다.

# generator 함수는 yield를 사용함으로써 정의된다. 
def gen():
    yield 'hello'
    yield 'world!'

변수로 선언 후 사용해야 한다. 변수로 사용하지 않으면 first value만 반복된다.

make_gen = gen()
print(next(make_gen))  # 'hello'
print(next(make_gen))  # 'world!'
hello
world!
# 무한 반복 generator 예시
def infinite_gen():
    while True:
        yield "No end"

Lazy Evaluation- 메모리를 효율적으로 사용

list는 모든 데이터를 메모리에 적재하지만, generator의 경우, next() 메소드를 통해 차례로 값에 접근할 때마다

메모리에 적재하는 방식이다. / 다음 value가 yield되면 이전 value는 사라진다.

대용량 데이터를 활용할 때 매우 유용하다.

import sys
print("List 메모리 사용량")
print( sys.getsizeof([i for i in range(100)]) )
print( sys.getsizeof([i for i in range(10000)]) )
List 메모리 사용량
912
87624
print("Generator 메모리 사용량")
print( sys.getsizeof((i for i in range(100))) )
print( sys.getsizeof((i for i in range(10000))) )
Generator 메모리 사용량
88
88

generator expression - List comprehension과 결과는 같지만 한 개씩 접근 가능

sum, map과 같은 list에 적용할 수 있는 함수를 사용할 수 있다.

my_gen = ( i**2 for i in [1,2,3,4,5] )
my_gen
<generator object <genexpr> at 0x7f42242f5af0>
print( sum(my_gen) )
55

generator를 활용한 데이터 파이프라인 예시

출처 : python-generators-tutorial by dataquest

BeerID Name URL Style StyleID
1 Vanilla Cream Ale /homebrew/recipe/view/1633/vanilla-cream-ale Cream Ale 45
2 Southern Tier Pumking clone /homebrew/recipe/view/16367/southern-tier-pumk... Holiday/Winter Special Spiced Beer 85
3 Zombie Dust Clone - EXTRACT /homebrew/recipe/view/5920/zombie-dust-clone-e... American IPA 7
beer_data = "recipeData.csv" 

# generator 1
lines = (line for line in open(beer_data, encoding="ISO-8859-1"))
# generator 2
lists = (l.split(",") for l in lines)

# CSV 파일에서 컬럼명만 먼저 추출
columns = next(lists) 

# generator 3
beerdicts = (dict(zip(columns, data)) for data in lists)
next(beerdicts)
{'BeerID': '1',
 'Name': 'Vanilla Cream Ale',
 'URL': '/homebrew/recipe/view/1633/vanilla-cream-ale',
 'Style': 'Cream Ale',
 'StyleID': '45',
 'Size(L)': '21.77',
 'OG': '1.055',
 'FG': '1.013',
 'ABV': '5.48',
  ...}

References :