📝 이터레이터를 생성해주는 함수. 발생자라고도 한다.
__iter__
, __next__
메서드를 갖고 있다.return
키워드로 함수 종료시에도 StopIteration 예외가 발생한다.next()
함수 (또는 __next__
메서드)를 사용해 값을 하나씩 불러오며, 제너레이터는 다음번 next()
실행시까지 대기한다.def number_generator(stop):
n = 0 # 숫자는 0부터 시작
while n < stop: # 현재 숫자가 반복을 끝낼 숫자보다 작을 때 반복
yield n # 현재 숫자를 바깥으로 전달
n += 1 # 현재 숫자를 증가시킴
for i in number_generator(3):
print(i)
이터레이터로 같은 기능을 수행하는 코드와 어떻게 다른지 눈여겨보자.
def upper_generator(x):
for i in x:
yield i.upper() # 함수의 반환값을 바깥으로 전달
fruits = ['apple', 'pear', 'grape', 'pineapple', 'orange']
for i in upper_generator(fruits):
print(i)
return 키워드와 유사하게 사용할 수 있다.
아래의 두 코드는 기능적인 측면에서 동일하다:
def number_generator():
x = [1, 2, 3]
for i in x:
yield i
for i in number_generator():
print(i)
def number_generator():
x = [1, 2, 3]
yield from x # 리스트에 들어있는 요소를 한 개씩 바깥으로 전달
for i in number_generator():
print(i)
제너레이터 또한 이터러블처럼 동작하므로(__iter__
, __next__
) 위 코드 x
의 자리에 제너레이터를 지정할 수도 있다.
def number_generator(stop):
n = 0
while n < stop:
yield n
n += 1
def three_generator():
yield from number_generator(3) # 숫자를 세 번 바깥으로 전달
for i in three_generator():
print(i)
📝 Generator Expression
아래와 같이 표현식을 괄호로 묶어 제너레이터를 만들수도 있다:
>>> x=(n*2 for n in range(10))
>>> type(x)
<class 'generator'>
>>> x
<generator object <genexpr> at 0x000002AA7DBD7648>
>>> next(x)
0
>>> next(x)
2
>>> next(x)
4
값을 하나씩 생성하므로 메모리를 절약할 수 있다.
이제 병합 정렬에 도전해보자.
def merge_sort(collection: list) -> list:
def merge(left: list, right: list) -> list:
def _merge():
while left and right:
yield (left if left[0] <= right[0] else right).pop(0)
yield from left
yield from right
return list(_merge())
if len(collection) <= 1:
return collection
mid = len(collection) // 2
return merge(merge_sort(collection[:mid]), merge_sort(collection[mid:]))
정리하면 아래와 같이 풀 수도 있을 것 같다(최소한 기능적인 측면에서):
def merge(left: list, right: list) -> list:
def _merge():
while left and right:
yield (left if left[0] <= right[0] else right).pop(0)
yield from left
yield from right
return list(_merge())
def merge_sort(collection: list) -> list:
if len(collection) <= 1:
return collection
mid = len(collection) // 2
return merge(merge_sort(collection[:mid]), merge_sort(collection[mid:]))
맨 아래 함수가 리스트를 받아서 정렬을 수행하는 주 함수이다. 리스트를 반으로 나눠 재귀하고 이에 대해 merge
함수를 실행한다. 제너레이터를 생성하는 _merge()
함수를 받아 리스트로 만드는 함수다. merge
함수의 두 인수를 받아 양쪽에 원소가 존재하는동안(while) 양 쪽의 맨 앞 원소를 비교해 적은 값을 yield하고, 한쪽이 빈다면 양쪽에서 yield를 받는다. next()
는 StopIteration 예외를 발생시키니 yield from으로 처리한다. (yield는 값, yield from은 이터러블 객체로 기억하자.)
>>> x = (i for i in range(3))
>>> x.__next__
<method-wrapper '__next__' of generator object at 0x100e01e50>
>>> next(x)
0
>>> x.__next__()
1
>>> x.__next__()
2
>>> def numbers():
... yield from x
...
>>> for i in numbers():
... print(i)
...
>>>
이렇게 yield된 값을 모아서 리스트로 만들어 합치는 정렬이다.