파이썬

파이썬(python) - 이터레이터(Iterator)

티베트 모래여우 2020. 12. 28. 20:29
반응형

이터레이터(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 등)

반응형