Python] Generators

iterator 객체의 한 종류

제너레이터를 만드는 2가지 방법

  • 제너레이터 함수: 제네레이터를 만들기 위한 함수 정의
  • 제너레이터 표현식: 제너레이터를 만들기 위한 식

제너레이터의 최대 핵심이자 장점 -> lazy evaluation

제너레이터 함수

일반 함수와 다른 점은 yield로 인해 제너레이터 함수가 된다는 점.

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
def generator():
print('One!')
yield 1
print('Two!')
yield 2
print('Three!')
yield 3

gen = generator() #제너레이터 객체 생성

print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))

"""
One!
1
Two!
2
Three!
3
Traceback (most recent call last):
File "test.py", line 14, in <module>
print(next(gen))
StopIteration
"""

for문도 iterator을 이용하지만, Generator는 쓸때마다 next를 써야하는 불편함이 있기에 장점이 없다면 쓰고 싶지 않다. 그렇다면 써야하는 이유는 무엇일까?

결론적으로 말하자면 메모리 때문이다.

제너레이터를 사용하지 않았을 때]

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys

def pows(s):
r = []
for i in s:
r.append(i**2)
return r

N = range(1,10)
st = pows(N)

print(st)
print(sys.getsizeof(st)) #184

제너레이터 사용]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sys

def pows(s):
for i in s:
yield i**2

N = range(1,10)
st = pows(N)

for i in st:
print(i, end=' ')

print()
print(sys.getsizeof(st)) #112

메모리 사용량을 본다면 차이가 생겼음을 알 수 있다. 적은 데이터로도 저렇게 차이가 나는데 정말 많은 데이터를 가지고 한다면 큰 차이가 생길 것이다.

yield from

1
2
3
def pows(s):
for i in s:
yield i

위의 for문을 간단하게 해결할 수 있는 방법이 있다.(파이썬 3.3이상 지원)

1
2
def pows(s):
yield from s

s에 함수를 넣어도 되지만, for문으로 i의 값에 제곱을 한다거나 변화하는 방법을 바로 적용하지 못하는 점은 다소 아쉽다.

제너레이터 표현식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 제너레이터 함수
def show(s):
yield from s

s = [1,2,3,4,5]

res = show(s)

print(type(res)) #<class 'generator'>

for i in res:
print(i, end=' ')
print()


# 제너레이터 표현식
res = (i for i in range(1,6))

print(type(res)) #<class 'generator'>

제너레이터 표현식은 메모리를 효율적으로 사용할 수 있으며, 함수를 별도로 정의해야하는 불편함도 없다.

next()을 써야하는 불편함(?)은 있다고 생각하는가? lazy evaluation의 장점을 버리지 말자.

제너레이터 표현식 사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys

def sum(s):
sumV = 0
for i in s:
sumV += i
return sumV

s = [i for i in range(1, 11)]
res = sum(s)
print(res) #55

print(sys.getsizeof(s)) #184

위와 같이 쓰는 것을 제너레이터 표현식으로 사용하여 쓸 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys

def sum(s):
sumV = 0
for i in s:
sumV += i
return sumV

s = (i for i in range(1,11))
res = sum(s)
print(res) #55

print(sys.getsizeof(s)) #112

원하는 값도 얻으면서 메모리도 줄일 수 있다.