◎ 이터러블(Iterable) 및 이터레이터(Iterator)
1) 이터러블(Iterable)
이터러블이란 순회 가능한 모든 객체, 즉 list, tuple, set, dict, range, str, 문자열, 파일 등을 말한다. 쉽게 생각해서 for 문의 in 키워드 뒤에 올 수 있는 모든 값이라고 보면 된다. 이중 list, tuple, range, str처럼 순서가 존재하는 타입은 시퀀스 타입이라 하고 set, dict처럼 단순 여러개의 요소를 갖는 타입은 컬렉션(컨테이너) 타입이라 한다.
2) 이터레이터(Iterator)
이터레이터는 이터러블의 자식 클래스, 즉 이터레이터==이터러블이다. 그러나 이터레이터는 이터러블과 달리 상태를 유지한다는 점으로부터 큰 차이가 존재한다. 여기서 상태를 유지한다는 것은 값을 반환할 때 가장 최근에 반환된 값 다음의 값을 반환하는 것을 말한다. 이터레이터를 만들기 위해서는 iter() 함수를 사용하면 된다. 이터레이터의 값을 하나씩 가져오려면 next() 함수를 사용하면 된다. 만약 이터레이터의 모든 값이 반환된 이후에도 이터레이터의 값을 가져오려 한다면 StopIteration 예외가 발생한다.
test_list = [1, 2, 3, 4, 5]
iterator = iter(test_list)
print("iterator 출력: {}".format(iterator))
print()
print("<iterator 값 가져오기>")
for i in iterator:
print(i, end=' ')
print()
print("<StopIteration 출력>")
print(next(iterator)) # 모든 요소의 반환이 이미 이뤄졌으므로 예외 발생
2-1) 이터레이터는 각각 상태를 가지므로 모두 다르다
이터레이터는 앞서 말했듯이 상태를 가지고 있다. 그말은 즉슨 각각의 이터레이터마다 서로 다른 상태를 가진다는 말이다. 같은 이터러블을 기준으로 만들어진 두 이터레이터 A, B가 있다고 가정한 아래의 코드를 통해 확인해보자.
test_list = [1, 2, 3, 4, 5]
iteratorA = iter(test_list)
iteratorB = iter(test_list)
# 1. Iterator A
print("<Iterator A>")
print(next(iteratorA))
print(next(iteratorA))
print(next(iteratorA))
print()
# 2. Iterator B
print("<Iterator B>")
print(next(iteratorB))
print(next(iteratorB))
print()
# ※ 남은 요소 모두 출력
print("<남은 요소 모두 출력>")
print(list(iteratorA))
print(list(iteratorB))
위 코드의 실행 결과를 보면 우선 같은 리스트를 기준으로 만들어진 두 개의 서로 다른 이터레이터가 존재하고, 두 이터레이터의 상태는 서로 다르다는 것까지 확인이 가능하다.
◎ 제너레이터(Generator)
제너레이터란 쉽게 말해 이터레이터를 만드는 함수라고 생각하면 된다. 제너레이터를 사용하는 경우로는 가져올 데이터의 범위가 무제한이어서 모든 데이터를 가져올 수 없는 경우, 대량의 데이터를 처리해야 할 때 하나씩 처리하기 위한 경우 등 데이터가 대량이거나 무제한인 경우에 사용한다. 추가로 제너레이터는 이터레이터의 자식 클래스이다.
1) 제너레이터 생성 방법
제너레이터를 생성하는 방법으로는 yield를 사용하는 방법과 yield from을 사용하는 방법이 있다.
① yield 사용
def gen_yield():
yield 1
yield 2
yield 3
yield 4
yield 5
gen = gen_yield()
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
# 모든 요소가 반환됐으므로 StopIteration 발생
print(next(gen))
제너레이터가 호출되고 나서 제너레이터 함수 내부의 yield 키워드를 만나면 해당 값이 반환되고 함수 호출이 끝나게 된다. 이후 다시 호출이 되면 그 다음의 yield 키워드의 값을 반환하고 다시 호출이 종료된다. 모든 값이 반환된 이후에 호출하게 되면 이터레이터와 동일하게 StopIteration 예외가 발생한다.
② yield from 사용
def gen_yield():
test_list = [10, 20, 30, 40, 50]
yield from test_list
gen = gen_yield()
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
# 모든 요소가 반환됐으므로 StopIteration 발생
print(next(gen))
yield from은 yield 키워드를 하나씩 계속 사용하는 것과는 달리 이터러블 타입의 데이터로부터 요소를 하나씩 출력해주는 형태로 ①번의 경우보다 훨씬 깔끔한 코드 작성이 가능하다.