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