C.W.K.
Stream
Lesson 01 of 07 · published

Iterator 프로토콜 — __iter__ 와 __next__

~22 min · iterator, protocol, iter, next, StopIteration

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

iterator 를 정의하는 두 메서드

Python 에서 for x in ... 의 오른쪽에 올 수 있는 모든 건 *iterable*. 메커니즘은 단순 — iterable 은 __iter__ 메서드를 가지고 iterator 를 반환, iterator 는 __next__ 메서드로 다음 값 반환 (또는 다 떨어지면 StopIteration raise). list, tuple, dict, 파일, range, generator expression 다 이 프로토콜 따라. 두 메서드 이해하면 Python 의 모든 반복 메커니즘이 투명해져.

Iterable vs Iterator — 구분

Iterable — 반복될 수 있는 거. Iterator — 실제로 반복하고 있는 거. my_list 는 iterable, iter(my_list) 는 그 위의 iterator. for 루프는 대상에 iter() 를 호출해 iterator 를 얻고, StopIteration 까지 next() 를 반복. 대부분 iterator 는 iterable 도 함 — __iter__ 에서 self 반환. 근데 모든 iterable 이 iterator 는 아니야.

for 루프 — 까놓고 보면

for x in seq: 는 내부적으로 거의 정확히 — it = iter(seq)while True: 안에서 x = next(it) + try/except StopIteration: break. 알면 디버깅할 때 수동으로 단계 진행 가능, for 와 매끄럽게 합쳐지는 자체 iterator 만들 수 있고, 어떤 버그 (iterator 소진 같은) 가 왜 발생하는지 추론 가능.

주의: iterator 는 *한 번만* 반복할 수 있어. StopIteration raise 후엔 같은 iterator 의 next() 가 영원히 StopIteration. 같은 iterator 를 두 for 루프에 감싸면 둘째 루프는 0 반복. "왜 두 번째 pass 가 빈가요?" 버그의 가장 흔한 원인.

커스텀 iterator 만들기

__iter__ (관습상 self 반환) 와 __next__ (다음 값 반환 또는 StopIteration) 구현. 이제 for x in my_object 작동. lesson 2 에서 — generator 함수가 훨씬 쉬운 방법 제공. 근데 프로토콜 먼저 이해해야 표준 라이브러리 iterator 의 소스 읽고 뭐가 일어나는지 알 수 있어.

Code

iter() + next() — 수동 방식·python
items = [10, 20, 30]
it = iter(items)             # items 위의 iterator

print(next(it))              # 10
print(next(it))              # 20
print(next(it))              # 30

try:
    next(it)
except StopIteration:
    print("소진")

# next() 는 디폴트 값으로 예외 피할 수 있어
it = iter([1, 2])
print(next(it, None))        # 1
print(next(it, None))        # 2
print(next(it, None))        # None  — 예외 없음
for 루프가 진짜 하는 일·python
items = [10, 20, 30]

# 이거
for x in items:
    print(x)

# 이거랑 같음
it = iter(items)
while True:
    try:
        x = next(it)
    except StopIteration:
        break
    print(x)
Iterator 소진 — 한 번 쓰면 끝·python
items = [1, 2, 3]
it = iter(items)

list(it)                     # [1, 2, 3]
list(it)                     # []   <- 이미 소진

# 근데 *iterable* 자체는 다시 반복 가능 — 매번 새 iterator
for x in items:
    pass
for x in items:              # 잘 됨 — 매 루프마다 새 iter()
    print(x)
커스텀 iterator — 프로토콜 구현·python
class CountDown:
    def __init__(self, start):
        self.n = start

    def __iter__(self):
        return self            # iterator 가 자기 자신 반환

    def __next__(self):
        if self.n <= 0:
            raise StopIteration
        v = self.n
        self.n -= 1
        return v

for x in CountDown(3):
    print(x)
# 3
# 2
# 1

# iterable 소비하는 모든 거랑 작동
print(list(CountDown(5)))    # [5, 4, 3, 2, 1]
print(sum(CountDown(10)))    # 55
Iterable vs Iterator — 코드로 구분·python
lst = [1, 2, 3]              # iterable, iterator 가 아님
it = iter(lst)               # iterator (list.__iter__ 호출)

# iterator 는 next() 지원
print(next(it))              # 1

# iterable 은 (iter() 거치기 전엔) X
try:
    next(lst)
except TypeError as e:
    print(e)                 # 'list' object is not an iterator

# 대부분 iterator 는 iterable *도* — __iter__ 에서 self 반환
print(iter(it) is it)        # True

External links

Exercise

클래스 Repeater(value, times) 구현 — value 를 정확히 times 번 yield 하고 StopIteration. (a) for x in Repeater("hi", 3): 테스트. (b) list(Repeater(...)) 작동 확인. (c) 같은 Repeater 인스턴스에 list() 두 번 호출하면 첫 번엔 값, 두 번엔 빈 list — iterator 소진 보여.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.