Programming/Python

[Python 3.7] 파이썬 이터레이터, 제너레이터 개념과 차이점 (iterator, generator, yield, yield from)

Nirsa 2020. 2. 8. 23:33
반응형

 

  • 이터레이터(iterator) 개념

예전 글에서 for문을 설명할 때 이터레이터라는 단어를 사용한적이 있는데, 이터레이터는 list, tuple, range과 같은 시퀀스 객체의 요소를 하나씩 꺼낼 수 있는 객체에서 사용 가능 합니다. (시퀀스 객체 말고도 딕셔너리,세트도 반복 가능한 객체이기 때문에 이터레이터가 가능 합니다) 

# 이터레이터를 확인하기 위해 간단한 리스트 생성
test = [10, 20, 30]

# __iter__() 메서드를 사용하여 이터레이터를 쓸 수 있도록 설정
test_iter = iter(test)
# 또는 test_iter = test.__iter__()

# __next__() 메서드를 사용하여 요소를 하나씩 불러옴
print(test_iter.__next__())
print(test_iter.__next__())
print(test_iter.__next__())
print(test_iter.__next__())

결과값 : 10
결과값 : 20
결과값 : 30
결과값 : StopIteration 에러 발생

__iter__ 메소드를 사용하여 이터레이터를 사용할 수 있도록 정의하고난 후 __next__ 메소드를 하여 요소를 하나씩 출력 합니다. 요소는 총 3개이므로 마지막 4번째의 __next__는 더이상 꺼내올 요소가 없기에 StopIteration 에러를 발생 시킵니다. (iter와 next말고도 __getitem__도 있습니다)

또는 아래 코드처럼 테스트해볼 수 있습니다.

test = [10, 20, 30]

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)
test_next = next(test_iter); print(test_next)

결과값 : 10
결과값 : 20
결과값 : 30
결과값 : StopIteration 에러 발생

 

  • 제너레이터(generator) 개념 (yield, yield from)

제너레이터는 이터레이터를 생성해주는 함수인데, 가장 큰 차이점은 제너레이터는 yield를 사용한다는 것과 메모리를 적재하는 방식에 있어서 차이가 납니다. yield from은 요소를 하나씩 꺼내서 밖으로 전달해줍니다.

def gen():
    yield 10
    yield 20
    yield 30

test_gen = gen()
n = next(test_gen); print(n)
n = next(test_gen); print(n)
n = next(test_gen); print(n)
n = next(test_gen); print(n)

결과값 : 10
결과값 : 20
결과값 : 30
결과값 : StopIteration 에러 발생

위의 코드 실행 결과와 같이 제너레이터는 이터레이터를 생성하고 동작 시키는데, 결과로만 보면 똑같아 보이지만 이터레이터와 제너레이터의 가장 큰 차이점은 메모리를 적재하는 방식 입니다.

우선 간단한 yield from을 사용하여 요소를 하나씩 꺼내서 밖으로 전달해주는지 확인 후 아래에 이터레이터와 제너레이터의 차이를 작성 하겠습니다.

def gen():
    x = [10, 20, 30]
    yield from x

test_gen = gen()
n = next(test_gen); print(n)
n = next(test_gen); print(n)
n = next(test_gen); print(n)
n = next(test_gen); print(n)

결과값 : 10
결과값 : 20
결과값 : 30
결과값 : StopIteration 에러 발생

 

  • 이터레이터와 제너레이터의 차이

단순히 yield를 쓰냐 안쓰냐의 차이도 있지만 가장 큰 차이점은 이터레이터는 모든 동작을 완료한 후 결과를 한번에 메모리 적재 시키는것에 반해, 제너레이터는 각각의 yield에서 한번 실행 시킨 후 대기 상태에 들어가 결과를 반환, 이후 다음 코드를 진행하여 또다시 yield를 만날 경우 대기 상태에 들어가 결과를 반환하는 방식입니다.

# 이너레이터
def test_iter():
    start = 1
    x = iter(range(1, 4))
    for i in x:
        print(i)

test_iter()

 

아래는 제너레이터의 코드와 실행 흐름 입니다. yield가 호출될때마다 함수가 잠시 대기 상태에 들어가고 결과값을 출력 후 다시 함수가 실행 상태로 되돌아 갑니다. 이렇게 yield를 만날때마다 함수는 잠시 대기 상태에 들어가고 바깥에 값을 전달하며 실행 순서를 양보 합니다.

# 제너레이터
def gen(count):
    start = 1
    while start <= count:
        yield start
        start += 1

for i in gen(3):
    print(i)

 

아래는 제너레이터의 실행 흐름 입니다. 빨간색 화살표 → 파란색 화살표 순으로 보시면 됩니다.

아래 코드처럼 time.sleep을 이용하여 확실히 밖에 값을 전달하고 출력 후 함수로 되돌아온다는걸 알 수 있습니다. 아래 코드를 실행하면 1 출력 후 2초간 대기, 2 출력 후 2초간 대기, 3 출력 후 2초간 대기가 되는데, 함수엔 결과를 yield를 이용해 밖으로 보내주기만하고 출력하는 기능은 없습니다.

즉, yield를 이용해 값을 함수 밖으로 보내고 대기 상태에 들어간 후 실행 순서를 양보하여 함수 밖의 print(i)가 출력 후 실행 순서가 함수로 다시 되돌아온다는 것을 알 수 있습니다.

import time

def gen(count):
    start = 1
    while start <= count:
        yield start
        time.sleep(2) # yield start 실행 후 2초간 대기
        start += 1

for i in gen(3):
    print(i)

 

반응형