[python] 이터레이터 사용하기

반복 가능한 객체 알아보기

# 이터레이터는 값을 차례대로 꺼낼 수 있는 객체이다.
# 여태 range를 이용한 for문을 많이 사용했는데,
# for i in range(n): 은 0부터 n - 1까지 숫자를 만드는 게 아니라
# 0부터 n - 1까지 값을 차례대로 꺼낼 수 있는 이터레이터를 하나만 만들어낸다.
# 숫자들을 미리 만들면 메모리를 많이 사용하게 되므로 성능에 불리하다.
# 그래서 파이썬에서는 이터레이터만 생성하고 값이 필요할 때만 값을 만드는 방식을 쓰는데,
# 이러한 방식을 지연 평가 라고 한다.

# 먼저 반복 가능한 객체에 대해 알아보자.
# 반복 가능한 객체는 문자열, 리스트, 딕셔너리, 세트를 말한다.
# 즉, 요소가 여러 개 있고 한 번에 하나씩 꺼낼 수 있는 개체를 말한다.
# 객체가 반복 가능한지 확인하는 방벙븐 객체에 __iter__ 메소드가 있는지 확인해보는 것인데,
# dir함수를 사용하면 객체의 메소드를 확인할 수 있다.
print(dir([1, 2, 3]))  # [..., '__list__', ...] 와 같이 __iter__ 메소드가 들어있는 것을 확인할 수 있다.
it = [1, 2, 3].__iter__()  # 이터레이터를 변수에 저장하고 __next__ 메소드를 호출해보면 요소를 차례대로 꺼낼 수 있다.
print(it)  # <list_iterator object at 0x000002790A271CD0>
print(it.__next__())  # 1
print(it.__next__())  # 2
print(it.__next__())  # 3
# print(it.__next__())  # 이렇게 한번 더 호출하면 StopIteration 예외가 발생한다.
# 이터레이터는 하나씩 꺼내오다가 꺼낼 요소가 없으면 StopIteration 예외를 발생시켜 반복을 끝낸다.
# __iter__와 __next__ 메소드를 클래스에 구현하면 이터레이터를 만들 수 있다.
# 이 두 메소드를 모두 가진 객체를 이터레이터 프로토콜을 지원한다고 말한다.

# 반복 가능한 객체: 요소를 한 번에 하나씩 가져올 수 있는 객체 ex) 리스트, 튜플, range, 문자열, 딕셔너리, 세트
# 이터레이터: __next__ 메소드를 사용해 차례대로 값을 꺼낼 수 있는 객체
# 시퀀스 객체: 반복 가능한 객체이면서 요소의 순서가 정해져 있고 연속적으로 이어져있는 객체 ex) 리스트, 튜플, range, 문자열

이터레이터 만들기

# 이번에는 __iter__, __next__ 메소드를 구현해서 직접 이터레이터를 만들어보자.
# class 이터레이터이름:
#     def __iter__(self):
#         코드
#
#     def __next__(self):
#         코드
class Counter:
    def __init__(self, stop):
        self.current = 0  # 현재 숫자 유지, 0부터 지정된 숫자 직전까지 반복을 끝낼 숫자
        self.stop = stop  # 반복을 끝낼 숫자

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

    def __next__(self):
        if self.current < self.stop:  # 아직 반복을 끝내기 전일 때
            cur = self.current  # 반환할 숫자를 변수에 저장
            self.current += 1  # 현재 숫자를 1 증가
            return cur  # 숫자 반환
        else:  # 반복을 끝내야 할 때
            raise StopIteration  # 예외 발생


for i in Counter(3):
    print(i, end=' ')  # 0 1 2
print()

# 이터레이터는 언패킹이 가능하므로, Counter 객체를 변수 여러 개에 할당할 수 있다.
# 이 때 이터레이터가 반복하는 횟수와 변수의 개수는 같아야 한다.
a, b, c = Counter(3)
print(a, b, c)  # 0 1 2

# 여태 써오던 map도 이터레이터라서 언패킹으로 변수 여러 개에 값을 할당할 수 있었다.
# 반환값을 저장할 때 _ 에 저장하는 경우가 있는데,
# 이건 반환값을 무시하겠다는 관례적 표현이다.
_, b = map(int, ['1', '2'])
print(b)  # 2

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

# 지금까지는 __iter__ 과 __next__ 메소드를 구현해서 이터레이터를 만들었다.
# 이번엔 __getitem__ 메소드를 구현해 인덱스로 접근할 수 있는 이터레이터를 만들어보자.
# class 이터레이터이름:
#     def __getitem__(self, index):
#         코드
class Counter:
    def __init__(self, stop):
        self.stop = stop

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


print(Counter(3)[0], Counter(3)[1], Counter(3)[2])  # 0 1 2

for i in Counter(3):
    print(i, end=' ')  # 0 1 2
print()

# 이렇게 __init__ 메소드와 __getitem__ 메소드만 있는데도 동작이 잘 된다.
# 클래스에서 __getitem__ 메소드만 구현해도 이터레이터가 되며, __iter__, __next__ 메소드는 생략해도 된다.

iter, next 함수 활용하기

import random
# 이번에는 파이썬 내장 함수 iter, next에 대해 알아보자
# iter은 객체의 __iter__메소드를 호출해주고, next는 객체의 __next__메소드를 호출해준다.
it = iter(range(3))
print(next(it))  # 0
print(next(it))  # 1
print(next(it))  # 2
# print(next(it))  # StopIteration 예외가 발생

# iter은 반복을 끝낼 값을 지정하면 특정 값이 나올 때 반복을 끝낸다.
# 이 때는 반복 가능한 객체 대신 호출 가능한 객체를 넣어준다.
# 참고로 반복을 끝낼 값은 sentinel(감시병)이라고 부른다
# iter(호출 가능한 객체, 반복을 끝낼 값)
it = iter(lambda: random.randint(0, 5), 2)
# next(it)로 숫자를 만들다가 sentinel이 나오면 StopIteration 예외가 발생

# 2가 나올 때까지 계속 숫자를 만든다.
# 이렇게
for i in it:
    print(i, end=', ')

# next는 기본값을 지정할 수 있다.
# 기본값을 지정하면 반복이 끝나더라도 StopIteration 예외를 발생시키지 않고 기본값을 반환한다.
it = iter(range(3))
for i in range(4):
    print(next(it, 10))  # 0 1 2 10
# 이터레이터를 만들 때 __iter__, __next__ 또는 __getitem__ 메소드를 구현해야 한다는 것을 기억하자.

Updated:

Leave a comment