상세 컨텐츠

본문 제목

코루틴 (Coroutine) (1) - Iterator 와 Generator

개발/python-병렬처리

by Matthew0633 2022. 5. 11. 15:56

본문

Iterable 객체와 Iterator 객체

Iterator를 사용하는 이유는 이전 글에서 언급한적이 있는데, next 메소드 실행 시부터 원소를 하나씩 반환하므로, 미리 모든 원소를 메모리에 올려놓지 않아, 메모리 효율적인 특징이 있기 때문이다. Generator는 이렇게 유용한 Iterator를 생성할 수 있는 함수이다. Iterator 객체는 Iter 함수를 통해 생성될수도 있다는 점에서 Iterable 하기 때문에 (그러나 역은 성립하지 않는 것에 주의한다!) 우리는 for문에서 반복을 통해 Iterator를 사용할 수 있다

그렇다면 for문은 어떻게 동작할까. Iterator와 while 문을 활용하면 간단히 for문의 메커니즘을 구현해볼 수 있다. Iterable한 객체로 Iterator 객체를 생성한 후 while 문에서 next 메소드를 통해 반복적으로 원소를 하나씩 반환하고, StopIteration 예외가 발생했을 때는 종료해주면 된다!

이러한 비슷한 로직이 있기에 for문에서는 Iterator를 사용해도, StopIteration 에러가 발생하지 않는다고 이해할 수도 있다

w = iter([1,2,3,4,5]) # Iterator 객체 할당

while True:
  try:
    print(next(w))
  except StopIteration:
    break

Iterable 객체인지 확인하려면?

print(dir(t))                        # 가능은 하나 비효율적 방법
print(hasattr(t, '__iter__'))        # 추천 1
print(isinstance(t, abc.Iterable))   # 추천 2

이런 Iterator 객체를 생성할 수 있는 것이 Generator 인데.. 또다른 Generator의 필요성은 무엇일까?

  • Generator를 사용했을 때 메모리효율적으로 사용할 수 있는데, 데이터 양이 방대할 수록 사용하지 않았을 때와의 차이는 더욱 유의미해질 수 있다
  • 마지막 상태를 기억하는 특성 때문에, Coroutine 구현이 가능하여 동시성 제어가 가능하다

Generator 를 Class로 간단히 구현해보자

아래 코드는 문장의 단어들을 공백기준으로 나누어 단어를 하나씩 반환하는 Generator를 Class 구현한다

구현된 코드에서 Generator와 연관된 개념을 살펴보자. _idx 는 __next__ 메소드를 통해 마지막으로 출력한 단어가 몇번째인지 기억하는 역할을 한다. 원소를 모두 출력하면, StopIteration 에러를 발생시킨다.

첫번째 출력결과를 통해, WordSplitter 가 next 메소드를 가지기 때문에 generator 객체로 표시되는 것을 알 수 있다.

# next 사용
class WordSplitter:
    def __init__(self, text):
        self._idx = 0
        self._text = text.split(' ')
    
    def __next__(self):
        try:
            word = self._text[self._idx]
        except IndexError:
            raise StopIteration('Stopped Iteration.')
        self._idx += 1
        return word

wi = WordSplitIter('Do today what you could do tomorrow')

print(wi)        # <generator object WordSplitGenerator.__iter__ at 0x0000019CD705AE40>
print(next(wi))  # 원소 개수만큼 실행되면 이후에 StopIteration 발생

그런데 반복문과 yield keyword를 사용하면, Generator 객체로 정의하여, index를 따로 저장하지 않아도 next가 출력한 위치를 기억하고, StopIteration 에러가 발생하지 않게끔 한다. 이로 인해, 코드간결해진다

class WordSplitGenerator:
    def __init__(self, text):
        self._text = text.split(' ')
    
    def __iter__(self):
        # print('Called __iter__')
        for word in self._text:
           yield word                 # Generator

관련글 더보기

댓글 영역