[Python] 볼 때마다 헷갈리는 Iterable, Iterator, Generator 정리하기

Iterable vs Iterator vs Generator


다른 분들의 코드를 읽을 때마다, 내가 사용할 때마다, 헷갈리는 Iterable, Iterator, Generator를 이번 글을 작성해보면서, 마지막으로! (라는 다짐으로) 정리해봅니다. 잘 알고 있는 개념이라고 생각했지만, 다른 사람들로부터의 질문을 받았을 때, 나의 설명이 만족스럽지 못해 ‘아 내가 더 정확히 알아야 한다’ 는 메타인지로부터 출발하는 글입니다.


파이썬의 장점으로 꼽히는, '사용하기 쉬운 데이터 구조'들 덕분에 우리는 루프를 돌아야하는 알고리즘에 대해 손쉽게 코드를 작성 할 수 있습니다. 하지만, 때로는 나만의 객체(class)를 만들고 그 객체가 파이썬에 내장 되어있는 데이터 구조 처럼, 동작하기를 바랍니다.
요즘 들어, 제가 짜는 코드에서 이 욕구는 신경망 모델링을 할 때, 신경망 모델 객체에 데이터의 배치를 feeding 하는 객체를 생성하고 싶을 때, 넘쳐나게 됩니다. 이럴 때, 파이썬에 대한 기본 개념이 잘 잡혀있지 않은 상태에서 복잡한 코드를 짜려고 하는 시도를 하니, 신경망 모델 자체의 구조에 대해서도 복잡한데 이런 루프를 돌면서 반복적으로 일정 데이터를 넘겨주기 위한 간단한 기능을 가진 코드에 대해서도 비효율적으로 작성하게 됩니다. 이런 제 자신의 문제를 해결하기 위해 이번 기회에 Iterator 와 Generator 에 대해 확실하게 정리해보려고 합니다. (feat. 예제코드)

1. 이터러블: Iterable

Iterable 객체란? : 객체 안에 있는 원소(element)를 하나씩 반환 가능한 객체

파이썬이 제공하는 대부분의 내장 데이터 구조는 이터러블(Iterable)한 객체 입니다. 뿐만 아니라 우리가 만든 객체(class)도 Iterable 객체가 될 수 있습니다. 이터러블(Iterable)객체는 for 문과 같은 루프 뿐만 아니라, zip이나 map 과 같은 순서대로 처리할 입력이 필요한 곳에서도 사용 될 수 있습니다.



이터러블(Iterable) 객체는 iter() 라는 함수의 입력으로 들어갑니다. iter() 라는 함수는 다음에 설명될 이터레이터(Iterator)를 반환합니다.


2. 이터레이터: Iterator

Iterator 객체란? : Iterator의 __next__() 나 내장 함수인 next()를 부르면서, 원소(element)를 순차적으로 반환 할 숫 있는 객체

앞서, 설명드린대로 이터레이터(Iterator)는 iter()라는 함수가 반환하는 객체입니다. 그리고, 이터레이터(Iterator)는 반복적으로 __next__()나, next() 함수의 입력으로 들어가 호출하여, next()의 return 값인 원소(element)를 최종적으로 반환합니다.


이터레이터(Iterator)가 다음 원소를 계속 반환하다가, 끝에 다달아 반환할 원소가 없을 경우 예외문인 StopIteration이 발생하게 됩니다.


즉, 정리하면,

Iterable 객체 A → iter(A) → Iterator 객체 B → next(B) → element (data)


Question? 우리는 list 같은 이터러블(Iterable) 객체와 for 문을 쓰면서 한번도 iter() 나 next()를 보지 못했는뎁쇼????
파이썬의 for 문은 이터러블(Iterable) 객체를 만나, 내부적으로 iter() 함수를 호출하여, 이터레이터(Iterator)를 생성합니다. 이 생성된 이터레이터(Iterator)가 루프가 실행되면서 next() 를 호출하며 반복적인 데이터를 뽑아 낼 수 있게 되는 것입니다. 그리고 모든 원소가 뽑아지고 난 뒤에는 StopIteration 이 발생하며, for 문이 종료 됩니다.


위 설명을 코드로 풀어쓰면, 다음과 같습니다. 우리가 다음과 같은 for 문을 사용하면,

1
2
for element in iterable_object:
print(element)

위 코드는 다음과 같이 파이썬 내부적으로 다음과 같이 동작하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
# iter() 함수를 호출해, iterator 를 생성하고,
iterator_object = iter(iterable_object)

while True:
# next() 함수를 호출해, element 를 받아옵니다.
try:
element = next(iterator_object)
print(element)

# element 가 없을 시, StopIteration Exception 발생
except: StopIteration:
break

3. 이터러블(Iterable), 이터레이터(Iterator)와 친해지기

3-1. 간단 버전

파이썬 이터러블(Iterable) 내장 데이터 구조인 list 를 활용하여, 실습해 봅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> iterable_object = [1, 2, 3, 4, 5]
>>> iterator_object = iter(iterable_object)
>>> iterator_object
<list_iterator object at 0x104fe2278>
>>> next(iterator_object)
1
>>> next(iterator_object)
2
>>> next(iterator_object)
3
>>> next(iterator_object)
4
>>> next(iterator_object)
5
>>> next(iterator_object)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

3-2. 나만의 iterable, iterator객체

다음의 코드는 이름과 나이를 받는 데이터 객체입니다. 루프를 돌면서 각각 순서에 맞는 (이름, 나이)형태로 데이터를 반환하는 Iterable 객체입니다. 같은 기능을 하는 더욱 효율적인 코드를 짤 수 있겠지만, 공부한 iterable 과 iterator 를 적용해보는 class 입니다.


4. 제너레이터: Generator

제너레이터(Generator)란?: 특이한 (공식 문서에 ~~~ iterator) 이터레이터(Iterator)

정의에서도 보다시피, 이터레이터(Iterator)입니다. 즉, next() 함수를 만나 동작하는 함수입니다. 이 때, 제너레이터(Generator) 를 일반 함수와 다르게 하는 것이 yield 라는 문법입니다. yield 는 일반 함수의 return과 같이 값을 반환 하지만, return과 다르게 해당 함수(generator)가 종료하지 않고, 그대로 유지됩니다. 다음 순서의 제너레이터(Generator)가 호출되면, 멈추었던 yield 자리에서 다시 함수가 동작하게 됩니다.

요약하자면,

제너레이터(Generator) → next(제너레이터) → 제너레이터 함수 실행 → yield를 만나 next(제너레이터가) 호출된 곳으로 값을 반환 (제너레이터 종료 ❌) → 다시 next(제너레이터) → 멈추었던 yield부터 재실행


Question? 이런 특이하고, 어렵고, 처음엔 익숙하지 않은 제너레이터(Generator)를 왜 때문에 쓰는 겁죠???
제너레이터는 메모리를 아끼기 위해서 사용합니다!!! 다음의 코드에서 메모리 사용량의 비교를 통해 살펴 보겠습니다.


5. 제너레이터 (Generator)와 친해지기

제너레이터를 사용한 코드가 메모리 사용량 측면에서 얼마나 효율적인지 확인해보겠습니다. 다음의 코드는 999999까지의 숫자를 제곱하여 return 해주는 함수입니다. 첫번째, 일반적인 함수는 end 숫자까지 for 문을 돌면서 그 결과를 저장한 뒤에 return 해줍니다. 두번째 generator 는 for 문이 돌 때, yield 에서 해당 데이터만 리턴하게 되므로, 메모리 사용에서 효율적입니다. 다음의 실험에서도 쉽게 비교할 수 있습니다.

위 코드 실행결과:

1
2
Memory Usage when program start: 8.80859375
Memory Usage when program end: 56.0390625

위 코드 실행결과:

1
2
Memory Usage when program start: 8.78515625
Memory Usage when program end: 8.78515625

6. 마무리

이번 짧은 글에서, Iterable, Iterator, Generator 를 비교해보면서 그 개념과 사용 예제를 간단하게 살펴 보았습니다. 이름에서부터 헷갈릴 수 있는 각 객체에 대한 내용을 이 글을 통해 조금이나마 정리해볼 수 있는 기회가 되셨으면 좋겠으며, 작은 도움이 되셨으면 합니다.


7. Reference

[Python] 볼 때마다 헷갈리는 Iterable, Iterator, Generator 정리하기

https://emjayahn.github.io/2019/07/15/iterator-generator/

Author

Emjay Ahn

Posted on

2019-07-15

Updated on

2019-08-06

Licensed under

Comments