트랙
/
Python
Python
/
학습 과정
/
제너레이터
제너

제너레이터 에서 Python

1개의 연습 문제

제너레이터 소개

generator는 이터레이터의 특별한 한 종류인 제너레이터 이터레이터를 반환하는 함수 또는 표현식이에요. Generator-iterators는 지연 평가 방식이에요. 즉, values를 메모리에 저장하지 않고 필요할 때 값들을 _생성_해요.

제너레이터 함수는 다른 함수와 똑같이 생겼지만, 하나 이상의 yield 표현식을 포함해요. yield를 만나면 코드 실행이 잠시 멈추고, 현재 실행 상태(지역 변수와 try 문을 모두 포함)가 저장돼요. 제너레이터가 다시 실행되면 멈춘 지점부터 상태를 이어받아요. 호출할 때마다 초기화되는 일반 함수와는 달라요.

제너레이터 만들기

제너레이터는 다른 반복 함수나 재귀 함수와 비슷하게 만들지만, yield 표현식이 필요해요. 이건 조금 뒤에 자세히 살펴볼게요.

예를 들어 주어진 숫자 목록에서 _제곱_을 반환하는 함수를 생각해볼까요.
지금 작성된 코드는 값을 하나라도 반환하기 전에 모든 입력을 다 처리해야 해요:

>>> def squares(list_of_numbers):
...     squares = []
...     for number in list_of_numbers:
...         squares.append(number ** 2)
...     return squares

이 함수는 다음과 같이 제너레이터로 바꿀 수 있어요:

>>> def squares_generator(list_of_numbers):
...     for number in list_of_numbers:
...         yield number ** 2

이렇게 하는 이유는 모든 값을 한꺼번에 필요로 하지 않을 때 제너레이터를 쓰기 때문이에요.

지금 처리 중인 값만 계산하므로 메모리와 연산 능력을 아낄 수 있어요.

제너레이터 사용하기

제너레이터는 (엄밀히 말하면 generator-iterator예요. 아래 노트를 봐요.) iterator의 한 종류이고, Python에서 iterator나 iterable이 필요한 곳이면 어디든 쓸 수 있어요. iterable/iterator를 인자로 요구하는 _함수_나 _객체_도 포함돼요. 더 자세히 알아보려면 Python에서 이터레이터 만들기를 참고해요.

Note

제너레이터 이터레이터는 이터레이터의 특별한 부분 집합이에요. Iterators는 _이터러블_을 순회할 수 있게 해주는 메커니즘, 즉 프로토콜이에요. 제너레이터 이터레이터와 일반적인 Python iterables가 반환하는 이터레이터는 아주 비슷하게 동작하지만, 몇 가지 중요한 차이점이 있어요:

  • 지연 평가 방식이에요. 순회는 _한 방향_으로만 진행되고, 이전 값으로 되돌아갈 수 없어요.
  • 반환된 값을 순회하면 값이 _소비_돼요. 초기화되거나 메모리에 저장되지 않아요.
  • 정렬할 수 없고 뒤집을 수도 없어요.
  • 시퀀스 타입이 아니고, indexes도 없어요. 덧셈이나 뺄셈으로 이전 값이나 이후 값을 참조할 수 없고, 대괄호([]) 표기법이나 슬라이싱도 쓸 수 없어요.
  • len() 함수와 함께 쓸 수 없어요. 길이가 없기 때문이에요.
  • _유한_할 수도, _무한_할 수도 있어요. 무한 generator-iterator에서 모든 값을 모을 때는 조심해요!

squares_generator() 제너레이터를 사용하려면 이렇게 해요:

>>> squared_numbers = squares_generator([1, 2, 3, 4])

>>> for square in squared_numbers:
...     print(square)
...
1
4
9
16

제너레이터의 값은 next() 함수로도 만들거나 꺼낼 수 있어요. next()는 제너레이터 객체의 __next__() 메서드를 호출해서 제너레이터 코드를 yield 표현식까지 “진행”시키고, 그러면 값이 반환돼요.

>>> squared_numbers = squares_generator([1, 2])

>>> next(squared_numbers)
1
>>> next(squared_numbers)
4

generator가 완전히 소비되어 더 이상 반환할 값이 없으면 StopIteration 오류를 던져요.

>>> next(squared_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

이터러블과 제너레이터의 차이

제너레이터는 _이터레이터_의 특별한 부분 집합이에요. Iterators는 _이터러블_을 순회할 수 있게 해주는 메커니즘, 즉 프로토콜이에요. 제너레이터와 일반적인 Python iterables가 반환하는 이터레이터는 아주 비슷하게 동작하지만, 몇 가지 중요한 차이점이 있어요:

  • 제너레이터는 _한 방향_이에요. 이전 값으로 되돌아갈 수 없어요.

  • 제너레이터를 순회하면 반환된 값이 소비돼요. 초기화되지 않아요.

  • 제너레이터는 (지연 평가되기 때문에) 정렬할 수 없고 뒤집을 수도 없어요.

  • 제너레이터에는 indexes가 없어요. 그래서 덧셈이나 뺄셈으로 이전 값이나 이후 값을 참조할 수 없어요.

  • 제너레이터는 len() 함수와 함께 쓸 수 없어요.

  • 제너레이터는 _유한_할 수도, _무한_할 수도 있어요. 무한 제너레이터에서 모든 값을 모을 때는 조심해요.

yield 표현식

yield 표현식은 return 표현식과 아주 비슷해요.

return 표현식과는 달리, yield는 _특정 지점_에서 호출한 쪽에 값을 넘겨주고, 나머지 값의 계산과 반환은 요청이 있을 때까지 멈춰 둬요.

yield가 실행되면, 자신을 감싸고 있는 함수의 실행을 잠시 멈추고 그 시점의 함수 값을 반환해요.

그러면 함수는 스코프에 그대로 남아 있고, __next__()가 호출되면 다시 yield를 만날 때까지 실행이 재개돼요.

Note

함수 밖에서는 yield 표현식을 사용할 수 없어요.

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1

제너레이터를 쓰는 이유

제너레이터는 다양한 곳에서 유용하게 쓰여요.

큰 컬렉션을 다룰 때는 모든 값을 memory에 넣고 싶지 않을 수 있어요. 제너레이터를 쓰면 큰 데이터를 조각조각 처리하면서 메모리를 아끼고 성능을 높일 수 있어요.

어떤 처리나 계산이 _복잡_하거나 _비용이 많이 들_거나 _무한_할 때도 제너레이터가 아주 유용해요:

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

이제 infinite_sequence 객체에서 __next__()를 호출할 때마다 이전 숫자 + 1을 반환해요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요

제너레이터 배우기

연습이 잠겨 있어요

제너레이터 개념을 연습하려면 연습 문제 2개를 더 잠금 해제해요