[Python3] Iterable 과 Iterator 원리 | 프로삽질러
Iterable Object (이터러블 객체)
멤버들을 한 번에 하나씩 돌려줄 수 있는 객체 (feat 반복문을 활용해 데이터를 순회할 수 있는 객체) 로 내장 객체로 sequence 형 객체(list, tuple, str), collection 형 객체(dictionary, set) 등 반복성을 가진 객체를 예로 들 수 있습니다. 이터러블은 for 루프에 사용될 수 있고 zip(), map() 등 시퀀스 객체를 필요로 하는 다른 곳에 사용됩니다.
내장 메서드로 __iter__ 메서드를 가지는 객체는 모두 이터러블 객체입니다. list, tuple, str, dictionary 모두 내장 메서드로 __iter__ 메서드를 갖고 있습니다.
__iter__ 메서드는 iterator 객체를 리턴하며 호출합니다. 리스트 [1,2,3]의 __iter__ 메서드를 호출하면 __iter__ 메서드가 호출한 iterator 객체 <list_iterator> 의 주소 0x11ac18e5a30 가 리턴됩니다. 똑같이 iterable한 객체에 대하여 iter(iterable object) 를 실행하면 iterator 객체를 리턴할 수 있습니다.
Iterator (이터레이터)
__iter__(), __next__() 메서드를 가지는 객체를 Iterator 형 객체라고 부릅니다. __next__ 메서드는 iterable 객체의 다음 항목을 돌려주다가, 다음 항목이 더 없으면 StopIteration 예외를 일으킵니다.
iterable 객체인 [1,2,3] 에 대한 iterator 객체의 __next__ 메서드를 총 4번 호출해보았습니다. 원소가 3개이므로 처음 3번 메서드 호출 시 순서대로 1 -> 2 -> 3 을 반환하다가 다음 항목이 없으므로 StopIteration 예외를 일으킵니다. 내장함수 iter(iterable) 과 마찬가지로 next(iterator) 를 실행하면 __next__() 메서드와 동일하게 동작함을 볼 수 있습니다.
Iterator 는 __iter__ 메서드를 가지므로 iterable 이기도 합니다. Iterable 객체와 Iterator 객체는 __iter__메서드를 가지므로 'for ~ in Iterable' loop 를 통해 항목들을 가져올 수 있습니다.
python [for ~ in X] loop 호출 시,
1) X가 iterable 인지 확인 == __iter__ 메서드를 갖는지 확인
2) __iter__ 메서드를 갖는 iterable 인 경우, __iter__ 메서드를 호출해 iterator 객체 반환
3) iterator 객체가 StopIteration 예외 일으키기 전까지 __next__ 메서드 호출해 모든 항목 순회
단, Iterator 객체에서 한번 호출된 항목들은 호출된 즉시 메모리에 남지않고 버려집니다. 그래서 아래와 같이 for 문으로 iterator의 모든 항목을 순차적으로 호출 후, next() 를 호출하면 StopIteration이 일어나는 것을 볼 수 있습니다.
이러한 특징은 Iterator가 메모리 리소스를 효율적으로 쓰고 있음을 알 수 있습니다. 대표적으로 range() 함수를 예로 들 수 있습니다. 0부터 1,000,000 까지 수를 출력하는 코드가 필요하다고 생각해봅시다.
1) 0부터 0부터 1.000,000 까지 수를 리스트에 담아 for 문으로 출력하는 방법
2) range(1,000,000) 함수를 바로 for문 표현식에 적용시켜 출력하는 방법
리스트는 메모리에 모든 숫자를 저장하기 때문에 큰 숫자에 대해 단순 숫자를 출력할 때 리스트를 사용하게 된다면 상당한 메모리 낭비 -> 성능 저하로 이어지게 됩니다. 반면 iterable 객체인 range 함수를 바로 for문 표현식에 적용하면 iterator 객체가 호출되어 메모리 할당 없이 0부터 1,000,000 값을 출력하고 버리기 때문에 메모리 효율을 높일 수 있습니다.
Iterator (이터레이터) 만들기
iterable, iterator 개념에 대한 이해를 마쳤다면 직접 반복 가능한 성질을 가지는 Iterator 객체를 만들어 보겠습니다.
0부터 100까지 짝수만을 순차적으로 반복하여 내뱉는 Iterator 를 만들어보겠습니다! __iter__(), __next__() 메서드를 필수적으로 구현해야 합니다.
class EvenIterator:
def __init__(self):
self.index=0
def __iter__(self):
return self
def __next__(self):
if self.index<=50:
n=self.index*2
self.index+=1
return n
else: raise StopIteration
__iter__() 메서드는 __next__() 메서드를 가진 객체(Iterator)를 반환해야하는데 EvenIterator 클래스 자체에 __next__() 메서드를 구현했으므로 self를 리턴해줍니다. __next__() 메서드에서 self.index를 늘려가며 짝수를 반환하고, 짝수가 100이 넘어갈, self.index > 50 일 때 StopIteration 예외를 일으켜줍니다.
iterator=EvenIterator()
for even in iterator:
print(even, end=' ')
next(iterator)
EvenIterator 객체를 for 문 표현식으로 넘겨 Iterator 객체로 사용되게끔 출력문을 작성했다. 그 결과 의도한 대로 0 부터 100까지 짝수가 Iterator 로 반환된 이후 next() 함수를 호출하면 StopIteration 예외가 발생합니다.