파이썬(python) - 이터레이터(Iterator)
이터레이터(Iterator)란?
이터레이터는 순서대로 다음 값을 리턴할 수 있는 객체를 의미합니다. 자체적으로 내장하고 있는 next 메소드를 통해 다음 값을 가져올 수 있습니다.
여기서 '순서대로'라는 말 때문에 list, tuple과 같은 타입의 객체를 생각하셨을 수도 있는데, 이것과는 다릅니다.
자세한건 아래에서 알아봅시다.
선행학습 : 컬렉션(Collection) 타입과 시퀀스(Sequence) 타입
컬렉션 타입
- list, tuple, set, dictionary와 같이 여러개의 요소(객체)를 갖는 데이터 타입
시퀀스 타입
- list, tuple, range, str등과 같이 순서가 존재하는 데이터 타입
Iterable VS Iterator
내부 요소(member)를 하나씩 리턴할 수 있는 객체를 보고 Iterable하다고 합니다. 쉽게 생각하면 우리가 평소에 많이 사용하는 for문을 떠올리시면 됩니다.
_list = [1,2,3,4,5]
for i in _list:
print(i)
이렇게 for문을 통해 순회할 수 있는 객체를 Iterable하다고 생각하시면 됩니다. 대표적으로 위에서 잠깐 설명한 시퀀스 타입과 컬렉션 타입의 객체가 있습니다.
그럼 Iterable한 것과 Iterator는 무슨 차이가 있는걸까요?
쉽게 말하자면 Iterable한 것은 __next__ 메소드가 존재하지 않고 Iterator는 존재한다고 생각하시면 됩니다.
즉 __next__ 메소드로 다음 값을 반환할 수 있으면 Iterator, 없으면 Iterable한 객체입니다.
Iterable 객체를 Iterator로?
주목할 점은, Iterable한 객체를 Iterator로 만들 수 있다는 점입니다.(정확히는 내부에 __iter__라는 메소드가 있는 객체)
예제와 함께 살펴봅시다.
a = [1, 2, 3]
print(a.__next__) # AttributeError 발생
그냥 list 타입의 데이터는 __next__ 메소드가 없습니다. 따라서 에러가 발생합니다.
a = [1, 2, 3]
print(type(a)) # list 출력
a = iter(a)
print(type(a)) # list_iterator 출력
print(a.__next__()) # 첫 실행시 1 출력
하지만 이렇게 iter함수를 이용해 Iterator로 만들어 줄 수 있습니다.
Iterator로 바꾼 후에는 정상적으로 __next__함수가 작동하는 것을 볼 수 있습니다.
a = [1, 2, 3].__iter__()
print(type(a)) # list_iterator
또는 이렇게 직접 __iter__ 메소드를 호출해 Iterator로 만들어 줄 수도 있습니다.
a = [1, 2, 3]
a = iter(a)
print(a.__next__()) # 1 출력
print(a.__next__()) # 2 출력
print(a.__next__()) # 3 출력
print(a.__next__()) # StopIteration Exception 발생
저 __next__메소드를 이용해서 값을 계속 꺼내다보면 마지막 값에 도달하게 되는데, 마지막 값을 꺼내고 나서 __next__메소드를 호출하면 StopIteration이라는 예외가 발생합니다.
for문과 Iterator
눈치채신 분도 계시겠지만 파이썬의 for문은 내부적으로 Iterator를 생성하여 동작합니다.(__iter__ 메소드 이용)
예를들어 리스트를 순회하는 for문이라 하면, 해당 리스트의 Iterator를 생성한 다음 __next__메소드를 이용해 순회를 도는 방식입니다.
대충 그림으로 표현하자면 다음과 같습니다.
StopIteration 예외가 발생하면 순회를 마칩니다.
직접 Iterator 만들기
Iterator는 직접 만들 수도 있습니다.
위에서 말씀드렸듯 __iter__메소드를 구현한 클래스를 통해 Iterable한 객체를 만들 수 있고, __next__메소드를 구현한 클래스를 통해 Iterator를 만들 수도 있습니다.
간단한 예제와 함께 살펴봅시다.
예제에서는 숫자를 입력받아 1부터 입력받은 값까지 1씩 증가하는 Iterator를 만들어보겠습니다.
class Iterable:
def __init__(self, stop_number):
self.stop_number = stop_number # 종료 값
def __iter__(self):
return Iterator(self.stop_number)
우선 Iterable한 객체를 만들 수 있는 클래스를 먼저 만들었습니다.
__init__메소드를 통해 입력받은 값(stop)을 저장하고
__iter__메소드에서는 Iterator객체를 리턴하게끔 구현하였습니다.
그럼 이제 Iterator 클래스를 구현해야겠죠?
class Iterator:
def __init__(self, stop_number):
self.current_number = 0 # 현재 값
self.stop_number = stop_number # 종료 값
def __next__(self):
if self.current_number < self.stop_number:
self.current_number += 1
return self.current
else:
raise StopIteration
Iterator 클래스는 위와 같이 구성하였습니다.
__init__메소드는 Iterable 클래스와 비슷하니 설명을 생략하겠습니다.
__next__메소드에서 로직을 구현하였는데, 단순히 종료 값보다 현재 값이 작다면 현재 값에 1을 더하고 그 값을 리턴하는 로직입니다. 현재 값이 종료 값과 같거나 커지면 StopIteration 예외를 발생시킵니다.
class Iterable:
def __init__(self, stop):
self.stop = stop
def __iter__(self):
return Iterator(self.stop)
class Iterator:
def __init__(self, stop):
self.current = 0
self.stop = stop
def __next__(self):
if self.current < self.stop:
self.current += 1
return self.current
else:
raise StopIteration
test = Iterable(5).__iter__()
print(test.__next__()) # 1
print(test.__next__()) # 2
print(test.__next__()) # 3
print(test.__next__()) # 4
print(test.__next__()) # 5
print(test.__next__()) # StopIteration 예외 발생
전체적인 코드는 다음과 같습니다.
보시다시피 전체적으로 잘 동작하는 것을 확인할 수 있습니다.
참고로 위 코드에서 Iterable 클래스와 Iterator 클래스를 한번에 구현할 수도 있습니다.
class IterableAndIterator:
def __init__(self, stop_number):
self.stop_number = stop_number # 종료 값 지정
self.current_number = 0 # 현재 값 지정
def __iter__(self):
return self # 자기 자신 객체를 리턴
def __next__(self):
if self.current_number < self.stop_number:
self.current_number += 1
return self.current_number
else:
raise StopIteration
차이점이라면 __iter__메소드와 __next__메소드를 같이 구현했다는 점
그리고 __iter__메소드가 자기 자신의 객체를 리턴한다는 점 정도가 되겠습니다.
한번 직접 구현해보시면 어떤 원리로 동작하는지 이해가 좀 더 빨리 되니 아직 어떻게 구현하는지 감이 잘 안잡히시는 분들은 간단한 예제를 몇개 만들어보시길 추천드립니다.(1씩 값이 감소하는 Iterator 등)