ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 이터레이터(반복자)
    Python 2023. 10. 24. 12:16

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

    파이썬에서는 이터레이터만 생성하고 값이 필요한 시점이 되었을 때 값을 만드는 방식 사용

    > 데이터 생성을 뒤로 미루는 것(지연 평가)

     

    반복 가능한 객체 알아보기

    > 문자열, 리스트, 딕셔너리, 세트

    객체에 __iter__ 메서드가 들어 있는지 확인, dir 함수로 확인 가능

    >>> dir( [1, 2, 3] )
    ['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__',
    '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
    '__getstate__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__',
    '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__',
    '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__',
    '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend',
    'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

    __iter__ 호출

    >>> [1, 2, 3,].__iter__()
    <list_iterator object at 0x0000029200719900>

    리스트의 이터레이터를 변수에 저장한 뒤 __next__ 메서드를 호출하면 요소를 차례대로 꺼낼 수 있음

    >>> it = [1, 2, 3,].__iter__()
    >>> it.__next__()
    1
    >>> it.__next__()
    2
    >>> it.__next__()
    3
    >>> it.__next__()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

    요소를 계속 꺼내다가 꺼낼 요소가 없으면 StopIteration 예외 발생시켜 반복 끝냄

    >>> # 문자열
    >>> it = 'hello, world'.__iter__()
    >>> it.__next__()
    'h'
    >>> it.__next__()
    'e'
    >>> it.__next__()
    'l'
    >>> it.__next__()
    'l'
    >>> it.__next__()
    'o'
    >>> # 딕셔너리
    >>> it = {'a':1, 'b':2}.__iter__()
    >>> it.__next__()
    'a'
    >>> it.__next__()
    'b'
    >>> it.__next__()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    >>> # range
    >>> it = range(3).__iter__()
    >>> it.__next__()
    0
    >>> it.__next__()
    1
    >>> it.__next__()
    2
    >>> it.__next__()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

     

    for와 반복 가능한 객체

    반복 가능한 객체는 요소를 한 번에 하나씩 가져올 수 있는 객체

    이터레이터는 __next__ 메서드를 사용해 차례대로 값을 꺼낼 수 있는 객체

    둘은 별개의 객체이므로 구분해야 함

    반복 가능한 객체에서 __iter__로 이터레이터를 얻음

     

    이터레이터 만들기

    1. 객체.__iter__() 이터레이터 생성

    2. __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:   
                r = self.current            
                self.current += 1           
                return r                  
            else:        
                raise StopIteration
            
    for i in Counter(3):
        print(i, end=' ')
        
    # 실행 결과
    0 1 2

     

    이터레이터 언패킹

    a, b, c = Counter(3)
    print(a, b, c)
    0 1 2
    a, b, c, d, e = Counter(5)
    print(a, b, c, d, e)
    0 1 2 3 4

    이터레이터 언패킹 가능, Counter()의 결과를 변수 여러 개에 할당 가능

    이터레이터가 반복하는 횟수와 변수의 개수는 같아야 함

    map도 이터레이터이므로 a, b, c = map(int, input().split())처럼 언패킹으로 변수 여러 개에 값을 할당할 수 있음

     

    반환값을 _에 저장하는 이유

    >>> _, b = [0, 1]
    >>> b
    1
    
    >>> for _ in [0, 1, 2]:
    ...     print('hello')
    ...
    hello
    hello
    hello

    a, b = range(2)와 같음

    반환값을 언패킹했을 때 _에 할당하는 것은 특정 순서의 반환값 사용하지 않고 무시하겠다는 표현

    >>> a, _, c, d = range(4)
    >>> a, c, d
    (0, 2, 3)
    >>>

    위와 같은 코드는 언패킹했을 때 두 번째 변수는 사용하지 않겠다는 뜻

     

    인덱스로 접근할 수 있는 이터레이터 만들기
    # class 이터레이터이름:
    #     def __getitem__(self, 인덱스):
    
    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])
    
    for i in Counter(3):
        print(i, end=' ')
        
    # 실행 결과
    0 1 2
    0 1 2

     

    iter, next 함수 활용
    >>> it = iter(range(3))
    >>> next(it)
    0
    >>> next(it)
    1
    >>> next(it)
    2
    >>> next(it)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

     

    iter

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

    이 경우, 반복 가능한 객체 대신 호출 가능한 객체(callable) 넣어 줌

    반복을 끝낼 값은 sentinel이라고 부르는데, 감시병이라는 뜻 = 반복을 감시하다 특정 값이 나오면 반복을 끝냄

    ex) random.randint(0, 5)와 같이 0부터 5까지 무작위로 숫자를 생성할 때 2가 나오면 반복을 끝내도록 만들 수 있음

    > 호출 가능한 객체를 넣어야 하므로 매개변수가 없는 함수 또는 람다 표현식으로 만들어 줌

    >>> # iter(호출가능한객체, 반복을끝낼값)
    >>> import random
    >>> it = iter(lambda: random.randint(0, 5), 2)
    >>> next(it)
    3
    >>> next(it)
    4
    >>> next(it)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

    for 반복문에 넣어 사용할 수 있음

    >>> import random
    >>> for i in iter(lambda : random.randint(0, 5), 2):
    ...     print(i, end=' ')
    ...
    1 4 1 >>>

    iter 함수를 활용하면 if 조건문으로 매번 숫자가 2인지 검사하지 않아도 되므로 코드가 간단해짐

    다음 코드와 동작이 같음

    >>> import random
    >>>
    >>> while True:
    ...     i = random.randint(0, 5)
    ...     if i == 2:
    ...         break
    ...     print(i, end=' ')
    ...
    3 4 4 4 5 0 1 1 4 5 1 1 1 1 >>>

     

    next

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

    반복할 수 있을 때는 해당 값 출력, 반복이 끝났을 때는 기본값 출력

    >>> # next(반복가능한객체, 기본값)
    >>> it = iter(range(3))
    >>> next(it, 'cat')
    0
    >>> next(it, 'cat')
    1
    >>> next(it, 'cat')
    2
    >>> next(it, 'cat')
    'cat'
    >>> next(it, 'cat')
    'cat'

    예외가 발생하지 않고 cat 출력

     

    연습 문제 예제
    class MultipleIterator:
        def __init__(self, stop, multiple):
            self.stop = stop
            self.multiple = multiple
            self.current = 0
        
        def __iter__(self):
            return self
        
        def __next__(self):
            self.current += 1
            if self.current * self.multiple < self.stop:
                return self.current * self.multiple
            else:
                raise StopIteration
            
    for i in MultipleIterator(20, 3):
        print(i, end=' ')
    
    print()
    
    for i in MultipleIterator(30, 5):
        print(i, end=' ')
        
    # 실행 결과
    
    3 6 9 12 15 18 
    5 10 15 20 25

    'Python' 카테고리의 다른 글

    코루틴  (0) 2023.10.24
    제너레이터(발생자)  (0) 2023.10.24
    예외 처리  (0) 2023.10.23
    클래스(3)  (0) 2023.10.23
    클래스(2)  (0) 2023.10.18
Designed by Tistory.