http://www.flowdas.com/blog/iterators-in-python/
대부분의 파이썬 튜토리얼들과는 달리 파이썬 3.3을 중심으로 기술하고 파이썬 2를 포함한 하위 버전에서 차이를 보이면 따로 설명합니다.
파이썬의 시퀀스는 for
루프로 탐색할 수 있습니다.
>>> for e in [1,2,3]: # 리스트
... print(e)
1
2
3
>>> for e in (1,2,3): # 튜플
... print(e)
1
2
3
>>> for e in 'abc': # 문자열
... print(e)
a
b
c
for
루프는 시퀀스가 아닌 딕셔너리(dictionary)에서도 가능합니다.
>>> for k in {'a':1, 'b':2, 'c':3}: # 딕셔너리
... print(k)
b
c
a
이 경우 딕셔너리의 키를 탐색하게 되는데, 키가 전달되는 순서는 정해지지 않았습니다. 때문에 여러분이 직접 실행할 경우, 위의 예와는 다른 순서로 키가 인쇄될 수 있습니다. 이터레이터(iterator)를 제공한다면 시퀀스가 아닌 타입도 for
루프로 탐색할 수 있습니다. 이터레이터는 내장 함수 iter()
를 사용해서 얻습니다.
>>> iter({})
<dict_keyiterator object at 0x108f7afc8>
이렇게 얻은 이터레이터를 탐색하려면 내장 함수 next()
를 사용하면 됩니다.
>>> it = iter({'a':1, 'b':2, 'c':3})
>>> print(next(it))
b
>>> print(next(it))
c
>>> print(next(it))
a
>>> next(it)
Traceback (most recent call last):
File "<console>", line 1, in <module>
StopIteration
next()
를 사용해 한 번에 하나씩 탐색해 나가다가, StopIteration
예외가 발생하면 끝냅니다. 이러한 탐색 법에 대한 약속을 이터레이터 프로토콜(Iterator Protocol)이라고 부르는데, for
루프는 간결한 문법을 제공합니다.
>>> for e in container:
... print(e)
는 다음과 같은 while
루프와 동일한 결과를 줍니다.
>>> it = iter(container)
>>> while True:
... try:
... e = next(it)
... print(e)
... except StopIteration:
... break
사용자가 정의한 클래스가 이터레이터를 지원하려면 __iter__()
메쏘드를 정의하면 됩니다. iter()
는 객체의 __iter__()
메쏘드가 돌려주는 값을 이터레이터로 사용합니다. next()
에 이터레이터를 전달하면 이터레이터의 __next__()
메쏘드를 호출합니다. 때문에 최소한의 이터레이터는 이렇게 구성할 수 있습니다.
>>> class Range:
... def __init__(self, n):
... self.n = n
... self.c = 0
... def __iter__(self):
... return self # 이터레이터인 경우는 자신을 돌려주면 됩니다.
... def __next__(self):
... if self.c < self.n:
... v = self.c
... self.c += 1
... return v
... else:
... raise StopIteration
... next = __next__ # 파이썬 2 에서는 __next__ 대신 next 를 사용합니다.
>>> for x in Range(3):
... print(x)
0
1
2
Range
클래스는 이터레이터 외의 용도가 없습니다. 하지만 딕셔너리는 별도의 용도가 있고, 사실 딕셔너리는 이터레이터가 아닙니다.
>>> next({})
Traceback (most recent call last):
File "<console>", line 1, in <module>
TypeError: 'dict' object is not an iterator
딕셔너리를 for
루프에 직접 사용할 수 있는 이유는 딕셔너리 역시 __iter__()
메쏘드를 제공하고 있기 때문입니다. 비슷한 방법으로 RangeFactory
라는 클래스를 만들어보면:
>>> class RangeFactory:
... def __init__(self, n):
... self.n = n
... def __iter__(self):
... return Range(self.n)
>>> for x in RangeFactory(3):
... print(x)
0
1
2
__next__()
메쏘드를 제공하고 있지 않기 때문에 RangeFactory
의 인스턴스는 이터레이터가 아닙니다만, __iter__()
메쏘드를 통해 이터레이터(이 경우에는 Range
인스턴스)를 제공하고 있기 때문에 for
루프에서 사용될 수 있습니다. 사실은 이 용법 때문에 이터레이터에도 큰 의미 없는 __iter__()
메쏘드를 만들어주도록 요구하고 있습니다.
이렇게 for
루프에 이터레이터를 직접 요구하지 않고, 이터레이터를 만드는 객체를 요구함으로써, 많은 경우에 for
루프가 깔끔해지기는 하지만, 간혹 혼란을 야기할 수 있는 경우도 발생할 수 있습니다.
>>> r = Range(3)
>>> for x in r:
... print(x)
... if x == 1:
... break
0
1
>>> for x in r:
... print(x)
2
두 개의 for
루프는 동일한 Range
인스턴스를 사용하고 있습니다. 반면에 RangeFactory
를 사용하는 경우는:
>>> r = RangeFactory(3)
>>> for x in r:
... print(x)
... if x == 1:
... break
0
1
>>> for x in r:
... print(x)
0
1
2
이 경우는 for
루프를 시작할 때마다 새로운 이터레이터(Range
인스턴스)가 만들어집니다. 때문에 매번 새로운 루프가 시작되는 것이지요. for
루프가 이터레이터를 재사용하는 것으로 보일 때는, 인스턴스가 이터레이터인지 이터레이터를 제공하는 객체인지를 확인하는 것이 중요할 수 있습니다.
프로그래밍을 하다 보면 한쪽에서 생산하고(produce), 다른 쪽에서 소비(consume) 하는 상황을 자주 만납니다. 이를 생산자-소비자 패턴(Producer-Consumer Pattern)이라고 하는데, 이터레이터는 소비자 쪽의 코드를 함축적이면서도 자연스럽게 기술할 수 있는 방법을 제공합니다. 이제 필요한 것은 생산자 쪽의 코드를 자연스럽게 구성할 수 있는 방법입니다. 다음에 다룰 제너레이터(Generator) 가 파이썬의 대답입니다.
'Program > Python' 카테고리의 다른 글
파이썬 이터레이터 (0) | 2017.10.23 |
---|---|
파이썬 제너레이터 generator (0) | 2017.10.23 |
MongoDB 에 데이터 저장하고 불러오기 (0) | 2017.10.02 |
Python을 활용한 텍스트 마이닝 11.텍스트 분석-감성 분석(Sentiment Analysis) 4편 (0) | 2017.09.11 |
Python을 활용한 텍스트 마이닝 10.텍스트 분석-감성 분석(Sentiment Analysis) 3편 (0) | 2017.09.11 |