-
이터레이터(반복자)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