이터레이터와 제너레이터

hodu·2022년 10월 5일
0

python

목록 보기
11/17
post-thumbnail

나를 몇주째 괴롭히고 있는 개념들을 오늘 한번에 정리해보려고 한다.
정말 알듯하면 까먹고 다시 알 것 같으면 헷갈리는 개념이라 잊지않게 자주 읽어보기!

1. 이터레이터

이터레이터라는 개념을 알고가기 전에, 우리는 이터러블(iterable)의 뜻을 명확하게 하고 가야한다.

1-1. iterable은 무슨 뜻일까?

영어 뜻 그대로 순회할 수 있다는 뜻이다.
즉, 멤버를 하나씩 차례로 반환하는 object를 의미한다.

그렇다면, 파이썬의 자료형에서는 순회할 수 있는 자료형은 무엇이 있을까?
list, str, tuple, dict, file 등이 있다.

우리가 for문을 이용해서 다음 값으로 반환이 가능한 객체들을 의미하는 것이다.

1-2. 그렇다면, iterable한 것은 iterator일까?

정답은 아니다.
이유는 다음 코드를 살펴보자.

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()는 이터레이터의 필수조건으로 해당 메서드를 통해 다음값을 가져올 수 있게 한다.

1-3. 그래서, 이터레이터가 뭔데?

이터레이터는 iter, next, getitem 메서드가 구현되어 있으며 next 메서드를 통해 데이터를 순차적으로 호출 가능한 object이다.

그래서 next()로 다음 값을 호출할 수 없는 list, str, tuple 등은 iterable하다라고 말한다.

1-4. iterable을 iterator로 만들 순 없을까?

당연히 가능하다.
아까 작성했던 리스트 l을 이터레이터로 만들어보자.

l = iter(l)
print(type(l))

# <class 'list_iterator'>

iter()라는 메서드를 이용하면 이터레이터로 변경할 수 있다.

1.5. 그러면, 이터레이터는 어떤 자료형일까?

  • 우리가 아까 어떤 메서드를 호출해서 다음 값을 가져왔을까?
  • iterable한 값을 어떻게 iterator로 만들었을까?

잘 생각해보자.

우리는 next와 iter라는 '메서드'를 통해서 원하는 행동들을 만들어냈다.
내부 메서드가 있으므로 이터레이터는 '클래스'이다.

1-6. (심화학습) 이터레이터 클래스를 만들어보자

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

이것까지 구현할 수 있다면 당신은 이터레이터를 완벽하게 이해했다!

한 번 구현해보자. 정답은 맨 아래에 있다.

2. 제너레이터(Generator)

다음으로는 제너레이터를 알아보자.
제너레이터의 정의는 '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는 매우 비슷한데 차이점을 알아보자.

2-1. yield와 return의 차이

return은 함수를 끝낸다. 반면 yield는 상태를 기억하고, 다음 값을 내보내준다.
코드에서 보다시피 yield를 이용하여 제너레이터를 호출했지만 함수가 끝나지 않았고, 다시 호출할 경우 다음 값을 내보내주게 된다.

2-2. 제너레이터는 어떤 자료형일까?

(이미 첫줄에 답이 나와있다ㅋㅋ)

  • 제너레이터는 Iterator를 구현해주는 "함수"이다.
  • return과 비슷한 yield라는 키워드를 통해 다음 값을 가져올 수 있음

즉, 제너레이터는 '함수'이다.

2-3. (심화학습) 제너레이터를 구현해보자

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
profile
안녕 세계!

0개의 댓글