나를 몇주째 괴롭히고 있는 개념들을 오늘 한번에 정리해보려고 한다.
정말 알듯하면 까먹고 다시 알 것 같으면 헷갈리는 개념이라 잊지않게 자주 읽어보기!
이터레이터라는 개념을 알고가기 전에, 우리는 이터러블(iterable)의 뜻을 명확하게 하고 가야한다.
영어 뜻 그대로 순회할 수 있다는 뜻이다.
즉, 멤버를 하나씩 차례로 반환하는 object를 의미한다.
그렇다면, 파이썬의 자료형에서는 순회할 수 있는 자료형은 무엇이 있을까?
list, str, tuple, dict, file 등이 있다.
우리가 for문을 이용해서 다음 값으로 반환이 가능한 객체들을 의미하는 것이다.
정답은 아니다.
이유는 다음 코드를 살펴보자.
l = [1, 2, 3, 4, 5]
for i in l:
print(i)
# 1
# 2
# ...
# 5
for문을 이용하면 뭔가 순회하는 것 처럼 보인다!
똑같은 코드를 아래와 같이 작성해보자.
print(next(l))
# TypeError: 'list' object is not an iterator
next로 호출하였더니 iterator가 아니라고 한다!
그럼 이유가 뭘까?
우선 for문을 이용한 '순회'는 range로 생성되었기 때문에 순회하는 것 처럼 보이지만, 실제로 next라는 메서드를 이용해서 다음 값을 가져와달라 직접 요청을 하면 이터레이터가 아니라는 TypeError가 발생한다.
+)next()
는 이터레이터의 필수조건으로 해당 메서드를 통해 다음값을 가져올 수 있게 한다.
이터레이터는 iter, next, getitem 메서드가 구현되어 있으며 next 메서드를 통해 데이터를 순차적으로 호출 가능한 object이다.
그래서 next()로 다음 값을 호출할 수 없는 list, str, tuple 등은 iterable하다라고 말한다.
당연히 가능하다.
아까 작성했던 리스트 l을 이터레이터로 만들어보자.
l = iter(l)
print(type(l))
# <class 'list_iterator'>
iter()
라는 메서드를 이용하면 이터레이터로 변경할 수 있다.
잘 생각해보자.
우리는 next와 iter라는 '메서드'를 통해서 원하는 행동들을 만들어냈다.
내부 메서드가 있으므로 이터레이터는 '클래스'이다.
range함수를 generator로 구현해보는 코드이다.
range함수처럼 0부터 stop까지 나오는 코드를 구현을 해줘야한다.
class irange:
def __init__(self, n):
pass
def __iter__(self):
pass
def __next__(self):
pass
if __name__=="__main__":
for i,j in enumerate(irange(5)):
assert i == j
이것까지 구현할 수 있다면 당신은 이터레이터를 완벽하게 이해했다!
한 번 구현해보자. 정답은 맨 아래에 있다.
다음으로는 제너레이터를 알아보자.
제너레이터의 정의는 'iterator를 생성해주는 함수'이며, 함수안에 'yield' 키워드를 사용하여 작성한다.
정의만 들으면 어려우니 코드를 한번 살펴보자.
def generator_test():
yield 1
yield 2
yield 3
g = generator_test()
print(next(g))
print(next(g))
print(next(g))
# 1
# 2
# 3
yield라는 키워드를 이용하여 상태를'기억'한다.
return과 yield는 매우 비슷한데 차이점을 알아보자.
return은 함수를 끝낸다. 반면 yield는 상태를 기억하고, 다음 값을 내보내준다.
코드에서 보다시피 yield를 이용하여 제너레이터를 호출했지만 함수가 끝나지 않았고, 다시 호출할 경우 다음 값을 내보내주게 된다.
(이미 첫줄에 답이 나와있다ㅋㅋ)
즉, 제너레이터는 '함수'이다.
range함수를 generator로 구현해보는 코드이다.
range함수처럼 0부터 stop까지 나오는 코드를 구현을 해줘야한다.
def grange(n):
pass
if __name__=="__main__":
for i, j in enumerate(grange(5)):
assert i == j
마찬가지로 이 함수를 구현할 수 있다면 제너레이터를 완벽하게 이해한 것이다!
모든 이터레이터는 제너레이터다.
제너레이터는 이터레이터이터를 만들어주는 '함수'라고 했다. 제너레이터는 이터레이터를 만드는 방법 중 하나이기 때문에 모든 제너레이터는 이터레이터라고 할 수 있다.
class irange:
def __init__(self, n):
self.current = 0
self.stop = n - 1
# Iterator 객체를 리턴해줘야 함
def __iter__(self):
return irange(self.stop)
def __next__(self):
if self.current <= self.stop:
temp = self.current
self.current += 1
return temp
else:
raise StopIteration('range 범위가 종료되었습니다.')
def grange(n):
current = 0
stop = n - 1
while current <= stop:
yield current
current += 1
if __name__=="__main__":
for i,j in enumerate(irange(5)):
assert i == j
for i, j in enumerate(grange(5)):
assert i == j