[Python] 이터레이터 iterator

이터레이터란?

이터레이터(iterator)는 값을 차례대로 꺼낼 수 있는 객체(obejct)이다.

만약 연속된 숫자를 미리 만들면 숫자가 적을 떄는 상관없지만 숫자가 아주 많을 때 메모리를 많이 사용하게 되므로 성능에 불리하게 된다.

그래서 파이썬에서는 이터레이터만 생성하고 값이 필요한 시점이 되었을 때 값을 만드는 방식을 사용한다. 즉, 데이터 생성을 뒤로 미루는 것인데 이러한 방식을 지연 평가(lazy evaluation)이라 한다.

반복 가능한 객체 알아보기

반복 가능한 개체인지 확인을 위해 dir을 사용하여 __iter__ 메서드가 들어있는지 확인해본다.

print(dir([1, 2, 3]))

print([1, 2, 3].__iter__)

it = [1, 2, 3].__iter__()
print(it.__next__())
print(it.__next__())
print(it.__next__())
# print(it.__next__()) # StopIteration Exception

  • dir로 확인해보면 __iter__ 메서드가 들어있다. 리스트에서 __iter__를 호출해보면 이터레이터가 나온다.
  • 리스트의 이터레이털르 변수에 저장한 뒤 __next__ 메서드를 호출해보면 요소를 차례대로 꺼낼 수 있다.
  • 리스트의 범위를 벗어나게 되면 StopIteration 예외가 발생한다.

리스트 뿐만 아니라 문자열, 딕셔너리, 세트도 __iter__를 호출하면 이터레이터 객체가 나온다. 그리고 이터레이터에서 __next__를 호출하면 차례대로 값을 꺼낸다.

클래스에 __iter____next__ 메서드를 모두 구현하면 이터레이터를 만들 수 있다. 특히 __iter__, __next__를 가진 객체를 이터레이터 프로토콜(iterator protocol)을 지원한다고 말한다.

정리하면 반복 가능한 객체는 요솔르 한 번에 하나씩 가져올 수 있는 객체이고, 이터레이터는 __next__ 메서드를 사용해서 차례대로 값을 꺼낼 수 있는 객체이다.

시퀀스 객체와 반복 가능한 객체의 차이

리스트, 튜플, range, 문자열은 반복 간으한 객체이면서 시퀀스 객체이다. 하지만, 딕셔너리와 세트는 반복 가능한 객체이지만 시퀀스 객체는 아니다.

시퀀스 객체는 요소의 순서가 정해져 있고, 연속적(sequence)으로 이어져 있어야 하는데, 딕셔너리와 세트는 요소(키)의 순서가 정해져 있지 않기 떄문이다.
따라서 시퀀스 객체가 반복 가능한 객체보다 좁은 개념이다.

즉, 요소의 순서가 정해져 있고 연속적으로 이어져 있으면 시퀀스 객체, 요소의 순서와는 상관없이 요소를 한 번에 하나씩 꺼낼 수 있으면 반복 가능한 객체이다.

이터레이터 만들기

class Counter:
    def __init__(self, stop):
        self.current = 0  # 현재 숫자
        self.stop = stop  # 반복을 끝낼 숫자

    def __iter__(self):
        return self  # 현재 인스턴스를 반환

    def __next__(self):
        if self.current < self.stop:
            r = self.current
            self.current += 1
            return r
        else:
            raise StopIteration
            
            
for i in Counter(3):
    print(i, end=" ")

class CountDown:
    def __init__(self, start):
        self.start = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.start > 0:
            num = self.start
            self.start -= 1
            return num
        else:
            raise StopIteration


for i in CountDown(5):
    print(i, end=" ")
  • __iter__ 메서드ㅏ를 만드는데 여기서는 self만 반환하면 된다.

  • __next__ dㅔ서는 조건에 따라 숫자를 만들어내거나 StopIteration 예외를 발생시킨다.

이터레이터는 언패킹(unpacking)이 간으한데 이터레이터가 반복하는 횟수와 변수의 개수가 같아야 한다.

__getitem__ : 인덱스로 접근할 수 있는 이터레이터 만들기

## __getitem__ 메서드 사용
class CountDown:
    def __init__(self, stop):
        self.stop = stop

    def __getitem__(self, index):
        if index < self.stop:
            return self.stop - index
        else:
            raise IndexError


print(CountDown(3)[0], CountDown(3)[1], CountDown(3)[2])

for i in CountDown(3):
    print(i, end=" ")
  • __getitem__ 메서드를 구현하면 인덱스로 접근할 수 있는 이터레이터가 만들어진다.
  • 클래스에서 __getitem__ 만 구현해도 이터레이터가 되며 __iter__, __next__는 생략해도 된다.

iter, next 함수 활용하기

iter

iter는 반복을 끝낼 값을 지정하면 특정 값이 나올 때 반복을 끝낸다.

반복을 끝낼 값을 sentinel이라고 부르는데 감시병이라는 뜻이다. 즉, 반복을 감시하다가 특정 값이 나오면 반복을 끝내는 것이다.

import random

it = iter(lambda: random.randint(0, 5), 2)  # 2가 나오면 끝난다

print(next(it))
print(next(it))
print(next(it))
print(next(it))
import random

for i in iter(lambda: random.randint(0, 5), 2):
    print(i, end=" ")

next

next는 기본값을 지정할 수 있다. 기본값을 지정하면 반복이 끝나더라도 StopIteration이 발생하지 않고 기본값을 출력한다.

즉, 반복할 수 있을 때는 해당 값을 출력하고 반복이 끝나면 기본값을 출력한다.

it = iter(range(3))

print(next(it, 10))
print(next(it, 10))
print(next(it, 10))
print(next(it, 10))  # 반복이 끝나면 기본값 출력

예제 1 - 배수 이터레이터 만들기

class MultipleIterator:
    def __init__(self, stop, multiple):
        self.stop = stop
        self.multiple = multiple
        self.current = 1

    def __iter__(self):
        return self

    def __next__(self):
        r = self.current * self.multiple
        if r < self.stop:
            self.current += 1
            return r
        else:
            raise StopIteration


for i in MultipleIterator(20, 3):
    print(i, end=" ")

print()

for i in MultipleIterator(30, 5):
    print(i, end=" ")

예제 2 - 시간 이터레이터 만들기

class TimeIterator:
    def __init__(self, start, stop):
        self.start = start
        self.stop = stop

    def __getitem__(self, index):
        if index < self.stop - self.start:
            time = self.start + index
            hour = time // 3600 % 24
            minute = time // 60 % 60
            second = time % 60
            return f"{hour:02}:{minute:02}:{second:02}"
        else:
            raise IndexError


start, stop, index = map(int, input().split())

for i in TimeIterator(start, stop):
    print(i)

print("\n", TimeIterator(start, stop)[index], sep="")

참고 자료

  • 파이썬 코딩 도장

좋은 웹페이지 즐겨찾기