제너레이터
제너레이터
이전 문서에서는 클래스로 이터레이터를 만들어보았다. 제너레이터는 이터레이터를 아주 간편하게 만드는 함수이다.
- 함수안에 yield를 사용한다. (return 과 비교)
- 제너레이터 표현식을 사용할 수 있다.
- 메모리 사용이 효율적이다.
제너네이터 사용하기
def season_generator(*args):
for arg in args:
yield arg
g = season_generator('spring', 'summer', 'autumn', 'winter')
yield는 첫번째 arg 값을 밖으로 리턴을 해주고 함수 실행을 잠깐 지연시키는 역할을 한다. 다음 __next__
값이 오면 다음 yield값을 리턴해 준다.
>>> print(g)
<generator object season_generator at 0x7fe1a40c3e40>
>>> print(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']
이제 g는 제너레이터 객체이다. 제너레이터는 이터레이터 안에 포함되어 있기 때문에 이터레이터 처럼 __next__
함수를 사용할 수 있다.
>>> print(g.__next__())
spring
>>> print(g.__next__())
summer
>>> print(g.__next__())
autumn
>>> print(g.__next__())
winter
>>> print(g.__next__())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
return vs yield
return은 작업 도중에 만나면 바로 함수 실행이 종료되고 결과 값을 반환해 준다. 반면에 yield는 작업을 나눠서 관리해줄 수 있다.
def func():
print("첫번째 작업 중...")
return 1
print("두번째 작업 중...")
yield 2
print("세번째 작업 중...")
yield 3
ge = func()
>>> data = ge.__next__()
첫번째 작업 중...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: 1
def func():
print("첫번째 작업 중...")
yield 1
print("두번째 작업 중...")
yield 2
print("세번째 작업 중...")
yield 3
ge = func()
>>> data = ge.__next__()
첫번째 작업 중...
>>> print(data)
1
>>> data = ge.__next__()
두번째 작업 중...
>>> print(data)
2
>>> data = ge.__next__()
세번째 작업 중...
>>> print(data)
3
제너레이터 표현식
제너레이터 표현식은 리스트를 만드는 것과 유사하다. 리스트를 만들 때 사용하는 []를 ()로 바꿔주면 끝난다.
double_generator = (i * 2 for i in range(1,10))
for i in double_generator:
print(i)
2
4
6
8
10
12
14
16
18
이렇게 함수를 만들지 않고서도 간단하게 제너레이터를 만들 수 있다.
메모리 사용 확인
숫자 1 - 10000 3배로 만든 결과 리스트 vs 제너레이터
- 리스트 : 데이터 저장에 필요한 메모리를 모두 사용
- 제너레이터 : 나중에 필요할 때 값을 만들어 사용 (메모리 매우 적게필요)
import sys
list_data = [i * 3 for i in range(1, 10000 + 1)]
generator_data = (i * 3 for i in range(1, 10000 + 1))
>>> print(sys.getsizeof(list_data))
87616
>>> print(sys.getsizeof(generator_data))
112
리스트는 876161 바이트 만큼의 크기를 차지하는데 이는 결과값을 메모리상에 저장하고 있기 때문이다.
반면에 제너레이터는 식만 만들 뿐 결과값은 저장하지 않고 __next__
함수가 호풀될 때마다 결과값을 만든다.
따라서 대용량의 데이터를 사용할 때에는 리스트 보다는 제너레이터를 사용하는 것이 좋다.