Iterable, Iterator

bong·2024년 6월 28일

Python

목록 보기
8/9

💡 Iterable, Iterator ?

Iterable, Iterator 모두 내장 모듈 collections.abc에 정의되어 있는 추상 클래스(ABC)이다. 따라서 하위 클래스에서 구현해야할 메서드를 __abstractmethods__에 가지고 있다. 또한 Iterator는 Iterable을 상속받는 하위 클래스로 Iterator는 Iterable의 특징을 모두 가진다.

# Iterable의 하위 클래스는 __iter__ 메서드를 구현해야함
print(Iterable.__abstractmethods__)
>>> frozenset({'__iter__'})

# Iterator의 하위 클래스는 __next__ 메서드를 구현해야함
print(Iterator.__abstractmethods__)
>>> frozenset({'__next__'})

# Iterator ⊂ Iterable
print(issubclass(Iterator, Iterable))
>>> True

👉 Iterable

  • iteration이 가능한 모든 객체
  • for문의 in 다음에 올 수 있는 모든 객체 (str, list, dict ... )
  • 생성 규칙 (Iterable을 상속 받기 위해서는.. 클래스가 Iterable이기 위해서는..)
    1. __iter__ 추상 메서드가 있어야한다
      • __iter__ 메서드가 클래스에 있으면 해당 객체에 iter() 내장함수를 적용하여 iterator를 생성할 수 있다
      • iter() 내장함수는 Iterable을 인자로 실행될 때마다 새로운 Iterator 인스턴스를 반환한다
li = [1, 2, 3, 4]
di = {'a': 1, 'b': 2}

li_iter_1 = iter(li)
li_iter_2 = iter(li)
di_iter = iter(di)
rg_iter = iter(range(10))

print(li_iter_1)
print(li_iter_2)
print(di_iter)
print(rg_iter)

>>> <list_iterator object at 0x000002297E3DBB20>
>>> <list_iterator object at 0x000002297E3DBB50>  # 위와 다른 Iterator 인스턴스 반환
>>> <dict_keyiterator object at 0x000002297E702430>
>>> <range_iterator object at 0x000001EB992EBD70>  # range()의 결과도 Iterable

👉 Iterator

  • '상태'를 기억하며 반환할 수 있는 마지막 값까지 원소를 필요할 때마다 하나씩 반환하는 것
    • 상태 == 순회하고 있는 위치
  • 생성 규칙
    1. __iter__ 추상 메서드가 있어야한다
      • iter() 내장함수는 Iterator가 인자인 경우 자기 자신 반환
    2. __next__ 추상 메서드가 있어야한다
      • Iterator를 next() 내장함수의 인자로 줬을 때 다음에 반환할 값을 정의해야 한다
      • 더 반환할 값이 없으면 StopIteration Exception을 발생시키도록 한다
        • Iterator는 마지막 원소까지 모두 반환하면 재사용할 수 없음
      • next() 내장함수 : Iterator의 다음 인자를 반환하고 상태(위치)를 다음으로 옮기는 기능
itr = iter([1, 2, 3, 4])

print(next(itr))
>>> 1  # 다음 반환 인자 = 2

for i in itr:
    print(i)
>>> 2
>>> 3
>>> 4

for i in itr:
    print(i)
>>>   # exception 발생은 안하지만 아무것도 나오지 않음

print(next(itr))
>>> StopIteration  # 더 반환할 값이 없음. 재활용 불가

👨🏻‍💻 Iterable, Iterator 만들어보기

class MyIterable:
    def __iter__(self):
        pass

class MyIterator:
    def __iter__(self):
        pass
    
    def __next__(self):
        pass

print(issubclass(MyIterable, Iterable))
>>> True
print(issubclass(MyIterator, Iterator))
>>> True

일단 위처럼 __iter__, __next__ 메서드를 정의해놓기만 하면 파이썬에서 Iterable, Iterator로 인식하기는 한다. 하지만 의미상으로도 맞지 않고, 쓸모도 없으므로 개념적 정의에 맞게 수정해보자.

class MyIterable:
    def __init__(self, n):
        self.n = n
    
    def __iter__(self):
        return MyIterator(self.n)

class MyIterator:
    def __init__(self, n):
        self.n = n
        self.cur = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.cur >= self.n:
            raise StopIteration
        self.cur += 1
        return self.cur

my_iterable = MyIterable(3)
my_iterator = iter(my_iterable)

위의 생성규칙을 토대로 직접 Iterable, Iterator를 만들어 사용할 수 있다.

⚙️ for 문의 동작

for 문의 동작 방식은 Iterable, Iterator와 깊은 관계가 있다. for 문의 아래와 같은 과정을 거쳐 순회를 수행한다.

  1. 키워드 in 뒤에 오는 Iterable에 iter() 내장함수를 써서 Iterator를 얻는다
    • iter(Iterable) -> 새로운 Iterator, iter(Iterator) -> 자기 자신
  2. 반환된 Iterator에서 next() 내장함수를 통해 반환값 하나씩 얻기
  3. 끝에 도달하여 next()의 결과로 StopIteration 예외가 발생하면 for 문 종료

📚 참조

0개의 댓글