값을 차례대로 꺼낼 수 있는 객체(object)로 반복자 라고도 한다.
지연 평가(lazy evaluation) : 이터레이터만 생성하고 값이 필요한 시점이 되었을 때 값을 만들어 데이터 생성을 뒤로 미루는 방식
연속된 숫자를 미리 만들면 숫자가 많을 때 메모리를 많이 사용하게 되므로 성능에 불리하다.
dir(객체)를 사용해 객체 안에 __inter__ 메서드가 들어있는지 확인한다.
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__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']
리스트의 이터레이터를 변수에 저장한 뒤 __next__ 메서드를 호출해보면 요소를 차례대로 꺼낼 수 있다. 이터레이터는 __next__로 요소를 계속 꺼내다가 꺼낼 요소가 없으면 StopIteration 예외를 발생시켜서 반복을 끝낸다.
>>> it = [1, 2, 3].__iter__()
>>> it.__next__()
1
>>> it.__next__()
2
>>> it.__next__()
3
>>> it.__next__()
Traceback (most recent call last):
File "<pyshell#48>", line 1, in <module>
it.__next__()
StopIteration
먼저 range에서 __iter__로 이터레이터를 얻는다. 한 번 반복할 때마다 이터레이터에서 next로 숫자를 꺼내서 i에 저장하고, 지정된 숫자 3이 되면 StopIteration을 발생시켜서 반복을 끝낸다.
반복 가능한 객체 : 요소를 한 번에 하나씩 가져올 수 있는 객체이다.
시퀀스 객체 : 요소의 순서가 정해져 있고 연속적(sequence)으로 이어져 있다.
딕셔너리와 세트는 요소(키)의 순서가 정해져 있지 않아 시퀀스 객체가 아니다.
range(횟수)처럼 동작하는 이터레이터다.
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 # 현재 숫자를 1 증가시킴
return r # 숫자를 반환
else: # 현재 숫자가 반복을 끝낼 숫자보다 크거나 같을 때
raise StopIteration # 예외 발생
for i in Counter(3):
print(i, end=' ')
# 실행 결과
0 1 2
다음과 같이 Counter()의 결과를 변수 여러 개에 할당할 수 있다.
이터레이터가 반복하는 횟수와 변수의 개수는 같아야 한다.
>>> 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
map도 이터레이터다. 그래서 a, b, c = map(int, input().split())처럼 언패킹으로 변수 여러 개에 값을 할당할 수 있다.
__getitem__ 메서드를 구현하여 인덱스로 접근할 수 있게 한다.
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
클래스에서 __getitem__만 구현해도 이터레이터가 되며 __iter__, __next__는 생략해도 된다(초깃값이 없다면 __init__도 생략 가능).
iter() : 반복 가능한 객체에서 이터레이터를 반환한다.
next() : 이터레이터에서 값을 차례대로 꺼낸다.
반복을 끝낼 값을 지정하면 특정 값이 나올 때 반복을 끝낸다. 이 때 반복 가능한 객체 대신 호출 가능한 객체(callable)를 넣어준다.
반복을 끝낼 값은 sentinel(감시병)이라고 부른다.
iter(호출가능한객체, 반복을끝낼값)
>>> import random
>>> it = iter(lambda : random.randint(0, 5), 2)
# 0부터 5까지 무작위로 숫자를 생성하다 2가 나오면 반복을 끝냄
>>> next(it)
0
>>> next(it)
3
>>> next(it)
1
>>> next(it)
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
next(it)
StopIteration
호출 가능한 객체를 넣어야 하므로 매개변수가 없는 함수 또는 람다 표현식으로 만들어준다.
기본값을 지정할 수 있다. 기본값을 지정하면 반복이 끝나더라도 StopIteration이 발생하지 않고 기본값을 출력한다.
next(반복가능한객체, 기본값)
>>> it = iter(range(3))
>>> next(it, 10)
0
>>> next(it, 10)
1
>>> next(it, 10)
2
>>> next(it, 10)
10
>>> next(it, 10)
10