수달이네 기술 블로그

16. 이터레이터, 제너레이터 본문

언어/Python

16. 이터레이터, 제너레이터

슬픈 수달이 2025. 9. 3. 21:37

이전에 range()를 사용할 때 리스트가 아닌 제너레이터를 반환한다고 배운적이 있다. 

이번엔 이터레이터와 제너레이터등의 반복문에서 주로 사용하는 객체를 배웠다.

🔍이터레이터(반복 가능한 객체)

반복가능한 객체란: list, tuple, range, dictionary, set등의 for루프와 함께 사용할 수 있는 것

  • x = [1,2,3] → __iter__ → iterator객체 → __next__ → 내용이 없을 경우 StopIteration예외를 반환한다.

__iter__(): 반복 가능한 객체 자신을 반환한다.

__next__(): 다음 반복을 위한반환한다. 내용이 없을 경우 StopIteration예외를 발생시킨다.

class MyCounter(object):
    def __init__(self, low, high):
        self.current = low
        self.high = high
    # 이터레이터 객체로서 자신을 반환한다.
    def __iter__(self):
        return self
    def __next__(self):
        # current가 high 보다 크면 StopIteration예외처리.
        # current가 high 보다 높으면 다음값을 반환한다.
        if self.current > self.high:
            raise StopIteration
        else:
            self.current += 1
            #self.current가 1을 증가했으나, 이전값을 출력하기 위해 리턴
            return self.current - 1

c = MyCounter(1,10)
for x in c:
    print(x, end = " ")

#1 2 3 4 5 6 7 8 9 10

raise: 강제로 예외를 발생시킴

위의 코드를 보면 class에 __iter__()와 __next__()메서드를 구현해줌으로써 iterator로서의 기능을 할 수 있게 되었다.

그러면 반복문에 넣어 다음 내용을 부를 때 자동으로 next()함수가 불러와져 다음 객체를 넘기고, 

만약 정해둔 high보다 커질 경우 StopIteration예외를 불러온다.

test = [10,20]
test_iter = iter(test)
test_next = next(test_iter)
print(test_next)
test_next = next(test_iter)
print(test_next)
test_next = next(test_iter)
print(test_next)

# 10
# 20
# Traceback (most recent call last):
#   File "C:", line 7, in <module>
#     test_next = next(test_iter)
# StopIteration

아래는 함수 없이 iter함수와 next함수의 기능을 보인 함수이다.

리스트를 iter()객체로 만든 후 next로 다음 값을 끌어온다.

범위를 벗어나면 StopIteration오류가 일어나는 것도 확인 가능하다.


🔍제너레이터

파이썬 2.3버전부터 도입된 객체로 yield키워드를 이용하여 함수로부터 반복가능한 객체를 생성한다.

def myGenerator():
    yield 'first'
    yield 'second'
    yield 'third'

for word in myGenerator():
    print(word)
    
# first
# second
# third

원래는 함수는 반복 불가능한 객체이나 반복 가능하게 만들어주는 yield객체가 생겼다.

def MyCounterGen(low, high):
    while low <= high:
        yield low
        low += 1

for i in MyCounterGen(1,10):
    print(i, end=' ')
    
#1 2 3 4 5 6 7 8 9 10

여기서 보면 yield 가 나오면 대기상태로 들어가 결과를 일단 반환한 후, 다음 코드를 실행시킨다.

def MyCounterGen(low, high):
    while low <= high:
        yield low
        low += 1
        print("func")

for i in MyCounterGen(1,5):
    print(i, end=' ')

# 1 func
# 2 func
# 3 func
# 4 func
# 5 func

아래를 보면 yield다음 func가 출력되야 하는데 함수를 나와 숫자를 출력한다.

위처럼 함수 밖으로 나가 내용을 출력시킨다.

yield 와 yield from 의 차이

import time
def gen():
    list1 = [10,20,30]
    yield list1
    time.sleep(1)
    print("gen()내의 list값", list1)

if __name__ == '__main__':
    test_gen = gen()
    n = next(test_gen)
    print("main()내의 list값", n)
    
#main()내의 list값 [10, 20, 30]

yield를 사용할 경우 list값 전체를 내보냈다.

만약 여기서 next를 한번 더 사용할 경우 리스트 객체 하나를 이미 내보냈으므로 다음 객체에서는 StopIteration오류가 날 것이다.

import time
def gen():
    list1 = [10,20,30]
    yield from list1
    time.sleep(1)
    print("gen()내의 list값", list1)

if __name__ == '__main__':
    test_gen = gen()
    n = next(test_gen)
    print("main()내의 list값", n)
    n = next(test_gen)
    print("main()내의 list값", n)
    
# main()내의 list값 10
# main()내의 list값 20

yield from을 사용하면 리스트를 반환하지만 리스트의 요소값을 하나씩 꺼내서 보여준다.

그렇기 때문에 next()가 한번더 불리더라도 아직 값이 남아있으므로 print("gen()내의 list값", list1) 해당 구문이 출력되지 않았다.

반환한 리스트를 전부 다 소진할 때 까지는 함수내의 다음 라인이 출력되지 않을 것이다.

🔍이터레이터 vs 제너레이터

이터레이터는 모든 동작을 완료한 후 결과를 한번에 메모리에 적재시키지만,

제너레이터는 각각의 yield를 실행시킨후 대기상태에 들어가 반호나하기를 반복하는 방식이다.