[Python] 이터레이터(iterator), 제너레이터(generator)
이터레이터 (iterator)
next 메소드를 통해 다음 값을 반환하는 객체
리스트, 튜플, string 등 for문을 사용해 하나씩 꺼내올 수 있다.
이러한 객체들을 iterable 객체라고 한다.
iterable 객체를 확인하는 방법은 아래와 같다.
li = ['가', '나', '다', '라', '마']
# __iter__가 있으므로 반복 가능하다
print(dir(li))
# 또는 hasttr 사용
print(hasattr(li, '__iter__'))
from collections import abc
# abc : abstract base class
# iterable 클래스를 상속 받았는지 확인
print(isinstance(li, abc.Iterable))
iterable 객체는 __iter__메서드를 갖고 있다.
itarable 객체는 iterator인지 확인해보자.
li = [1, 2, 3]
for i in li:
temp = i
next(li)
리스트를 for문을 사용해 값을 한 개씩 꺼내올 순 있지만,
next 메소드를 사용하니 아래와 같은 오류가 발생한다.
리스트는 iterator가 아니다!
반복 가능한 객체인데 iterator로 만들 수 없을까?
답은 iter 함수를 사용하면 된다.
iter함수는 iterable 객체를 받아 iterator로 만들어 준다.
사용해보자.
li = [1, 2, 3]
li_iterator = iter(li)
print(type(li))
print(type(li_iterator))
print(next(li_iterator))
print(next(li_iterator))
print(next(li_iterator))
print(next(li_iterator))
iter 함수로 리스트를 list_iterator 타입으로 변환했다.
dir로 이터레이터의 속성을 보면 __iter__와 __next__가 있다
print(dir(li_iterator))
따라서 위에서 언급한 iterator는 next 메소드를 사용해 다음 값을 반환할 수 있음을 확인할 수 있다.
모든 값을 반환한 후 next 메소드를 호출하니 StopIteration 예외가 발생한다.
iterator 또한 for문으로 반복가능하다.
for i in li_iterator:
print(i)
StopIteration 예외없이 출력된다!
iterable 객체의 실행순서는 다음과 같다.
iterable 객체 > __iter__() > iterator > __next__()
실제 for문도 iterable 객체가 iter()를 호출해서 iterable을 받아 next()를 호출하면서 실행된다.
(위에 for문에서는 iterable 객체가 아닌 iterator를 넘겨줬지만 이 또한 iter()를 호출하고 스스로를 반환한다.)
iter()는 __iter__ 메서드를 호출하고
next()는 __next__메서드를 호출한다.
__iter__메서드와 __next__메서드를 사용해 for문의 실행순서를 확인해보자. (iterator 구현)
class IterText:
def __init__(self, sentence):
self.sentence = sentence
self.idx = 0
def __iter__(self):
print('---iter() 동작---')
return self
def __next__(self):
if self.idx >= len(self.sentence):
raise StopIteration
print('---next() 동작---')
word = self.sentence[self.idx]
self.idx += 1
return word
it = IterText(['plz', 'study', 'continuously'])
for i in it:
print(i)
IterText 클래스에 리스트를 넘겨주면 __init__함수를 통해 sentence와 idx의 인스턴스 변수만 선언된다.
이후 for문을 실행시키면 it객체(iterable)가 __iter__함수를 실행한 후 __next__함수를 실행해 word를 하나씩 반환한다.
__iter__함수 없이 실행시키면 아래와 같은 오류가 발생한다.
위 코드에서 for문의 실행순서를 확인함과 동시에 클래스를 iterable하게 사용할 수 있다는 사실도 알 수 있다.
__iter__메서드와 __next__메서드만 구현하면 된다.
제너레이터(generator)
iterator를 생성해 주는 함수
위와 같이 간단한 예제는 메모리를 생각하지 않아도 된다.
하지만 데이터가 많은 리스트를 사용한다면, 메모리 부족이 생기기 쉽다.
제너레이터를 이용하면 모든 결과값을 한번에 메모리에 적재하지 않고 사용할 수 있다.
제너레이터 특징
- yield 키워드를 포함
- 한 번의 호출, 하나의 값을 반환
- 현재 상태 저장 후 정지
위 함수를 제너레이터로 구현해보자.
__iter__와 __next__ 메소드 없이 yield 키워드만 포함된 제너레이터 함수 작성만으로 가능하다.
class GeneratorText:
def __init__(self, sentence):
self.sentence = sentence
def gen(self):
print('---next() 동작---')
for word in self.sentence:
yield word
gt = GeneratorText(['plz', 'study', 'continuously'])
#제너레이터 객체 생성
gen_gt = gt.gen()
print(type(gen_gt))
#제너레이터 호출
word1 = next(gen_gt)
word2 = next(gen_gt)
word3 = next(gen_gt)
print(word1, word2, word3)
gen()은 yield 키워드를 포함하고 있으므로 제너레이터다.
print(dir(gen_gt))
제너레이터는 이터레이터를 생성해주는 함수라고 위에서 정의했다.
dir로 확인해보면 이터레이터가 갖고있는 __iter__와 __next__메소드를 갖고 있음을 확인할 수 있다.
제너레이터는 제너레이터 객체를 생성한 후 next(제너레이터 객체)를 통해 호출할 수 있다.
gt.gen()의 타입을 출력해보면 generator가 출력된다.
이를 사용하기 위해서는 next()를 사용하면 된다.
'---next() 동작---' 이 한 번만 출력된 것을 확인할 수 있다.
이는 처음 next(gen_gt)를 호출했을 때 출력되고 for문이 실행되면서 yield 키워드를 만나 word를 반환한 후 멈춰있기 때문이다.
다음 next()를 호출하면 for문이 다시 한 번 돌면서 word를 반환한 후 또 다시 멈춰있게 된다.
세 번째 next()를 호출하면 for문이 종료된다.
next()를 한 번 더 호출해보자.
word4 = next(gen_gt)
next 메소드를 호출하니 StopIteration 예외가 발생한다.
'Python > 파이썬 중급' 카테고리의 다른 글
[Python] 어노테이션(annotation), typing (0) | 2023.04.10 |
---|---|
[Python] 코루틴 (Coroutine) (0) | 2023.02.16 |
[Python] setdefault 함수 (0) | 2022.12.11 |
[Python] sorted, sort 함수 (0) | 2022.12.11 |
[Python] mutable(가변 객체), immutable(불변 객체) (0) | 2022.12.08 |