Generator and Iterator

이터레이터는 값을 차례대로 반환하는 객체로, __iter__()__next__() 메서드를 구현한다.
제너레이터는 yield 키워드를 사용하여 값을 하나씩 반환하는 함수로, 이터레이터를 생성한다.

제너레이터와 이터레이터의 주요 차이점

비교 항목이터레이터제너레이터
정의 방식__iter____next__ 메서드를 구현하는 클래스yield 키워드를 사용하는 함수
상태 저장인스턴스 변수를 통해 명시적으로 상태 저장함수의 실행 상태가 자동으로 저장
메모리 사용모든 상태를 명시적으로 저장해야 함필요한 값만 생성하여 메모리 효율적
구현 복잡도상대적으로 복잡함 (여러 메서드 구현 필요)매우 단순함 (일반 함수처럼 작성)
용도복잡한 이터레이션 로직이 필요한 경우간단한 순차적 데이터 생성
재사용성클래스로 구현되어 재사용 용이한 번 순회하면 소진됨
기능 확장성클래스이므로 추가 메서드와 속성 정의 가능함수 범위로 제한됨
성능상태 관리를 위한 추가 오버헤드 존재매우 가벼움
코드 가독성구조화된 형태로 명확하나 장황할 수 있음간결하고 직관적
양방향 통신메서드를 통해 구현 가능send() 메서드로 기본 제공

이러한 차이점들은 실제 사용에서 다음과 같은 의미를 가진다.

  1. 메모리 효율성:

    • 제너레이터는 모든 값을 미리 계산하지 않고 필요할 때마다 생성하므로, 대용량 데이터를 다룰 때 매우 효율적.
    • 이터레이터는 상태를 직접 관리해야 하므로 더 많은 메모리를 사용할 수 있다.
  2. 구현 복잡도:

    • 제너레이터는 일반 함수처럼 작성하면 되므로 구현이 매우 간단.
    • 이터레이터는 클래스를 정의하고 여러 메서드를 구현해야 하므로 더 복잡.
  3. 유연성:
    이터레이터는 클래스로 구현되므로 추가 메서드와 속성을 정의할 수 있어 더 복잡한 기능을 구현하기 쉽다.
    제너레이터는 함수 범위로 제한되므로 기능 확장이 상대적으로 제한적.

구현 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 이터레이터 예제
class NumberIterator:
    def __init__(self, limit):
        self.limit = limit
        self.counter = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.counter >= self.limit:
            raise StopIteration
        self.counter += 1
        return self.counter

# 제너레이터 예제
def number_generator(limit):
    for i in range(1, limit + 1):
        yield i

# 메모리 사용량 비교
def compare_memory_usage():
    import sys
    
    # 큰 숫자 시퀀스 생성
    n = 1000000
    
    # 이터레이터 클래스 인스턴스
    iterator = NumberIterator(n)
    # 제너레이터 객체
    generator = number_generator(n)
    
    print(f"이터레이터 객체 크기: {sys.getsizeof(iterator)} bytes")
    print(f"제너레이터 객체 크기: {sys.getsizeof(generator)} bytes")

# 실행 시간 비교
def compare_execution_time():
    import time
    
    n = 1000000
    
    # 이터레이터 실행 시간 측정
    start = time.time()
    iterator = NumberIterator(n)
    for _ in iterator:
        pass
    iterator_time = time.time() - start
    
    # 제너레이터 실행 시간 측정
    start = time.time()
    generator = number_generator(n)
    for _ in generator:
        pass
    generator_time = time.time() - start
    
    print(f"이터레이터 실행 시간: {iterator_time:f}초")
    print(f"제너레이터 실행 시간: {generator_time:f}초")

각각의 사용이 적합한 상황

이터레이터가 적합한 경우:

  • 복잡한 이터레이션 로직이 필요할 때
  • 상태를 더 세밀하게 제어해야 할 때
  • 이터레이션 객체를 재사용해야 할 때
  • 추가적인 메서드나 속성이 필요할 때

제너레이터가 적합한 경우:

  • 간단한 순차적 데이터 생성이 필요할 때
  • 메모리 효율성이 중요할 때
  • 대용량 데이터를 처리할 때
  • 코드를 간단하게 유지하고 싶을 때

StopIteration

이터레이터의 동작과 관련된 중요한 예외이다.
이터레이터가 더 이상 반환할 항목이 없음을 나타낸다. 이는 이터레이션의 종료 시점을 알리는 신호 역할을 한다.
제너레이터 함수에서는 마지막 yield 문이 실행된 후 자동으로 StopIteration이 발생한다.
사용자 정의 이터레이터를 만들 때, __next__() 메서드에서 더 이상 반환할 항목이 없을 때 StopIteration을 발생시켜야 한다.

실제 활용 StopIteration은 다음과 같은 상황에서 유용하다:

  • 커스텀 이터레이터 구현
  • 데이터 스트림의 종료 조건 제어
  • 무한 시퀀스에 종료 조건 추가
  • 이터레이터 체인에서 종료 시점 제어

발생 시점

  • 이터레이터의 __next__() 메서드가 더 이상 반환할 값이 없을 때 발생.
  • 리스트의 모든 요소를 순회한 후 다시 next() 함수를 호출하면 StopIteration이 발생한다.

주의사항

  • StopIteration은 일반적인 에러가 아니므로, 예외 처리에서 별도로 고려해야 한다.
  • 이터레이터가 한 번 StopIteration을 발생시키면, 해당 이터레이터는 재사용할 수 없다
  • for 루프 외부에서 이터레이터를 직접 다룰 때는 StopIteration 처리를 명시적으로 해야 한다.

For 루프와의 관계

  • for 루프는 내부적으로 이 예외를 사용하여 이터레이션의 종료를 감지한다.
  • for 루프는 StopIteration 예외가 발생할 때까지 next() 함수를 계속 호출한다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# for 루프의 내부 동작
numbers = [1, 2, 3]
iterator = iter(numbers)

try:
    while True:
        value = next(iterator)  # StopIteration이 발생할 때까지 반복
        print(value)
except StopIteration:
    pass  # 루프 종료

예시

  1. 기본적인 이터레이터와 StopIteration

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    class CountDown:
        """간단한 카운트다운 이터레이터"""
        def __init__(self, start):
            self.count = start
    
        def __iter__(self):
    
            return self
    
        def __next__(self):
            if self.count <= 0:
                # 더 이상 제공할 값이 없을 때 StopIteration 발생
                raise StopIteration
            self.count -= 1
            return self.count + 1
    
    ## 자동 처리되는 예
    
    countdown = CountDown(3)  
    for num in countdown: # StopIteration이 자동으로 처리됨  
        print(num)
    
  2. StopIteration의 내부 동작 확인

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    def demonstrate_stop_iteration():  
        """StopIteration이 발생하는 과정을 보여주는 함수"""
    
    ## 리스트로부터 이터레이터 생성
    
        numbers = [1, 2, 3]
        iterator = iter(numbers)
    
        try:
            print(next(iterator))  # 1
            print(next(iterator))  # 2
            print(next(iterator))  # 3
            print(next(iterator))  # StopIteration 발생
        except StopIteration:
            print("더 이상 원소가 없습니다!")
    
  3. 제너레이터에서의 StopIteration

    1
    2
    3
    4
    5
    6
    
    def number_generator(n):  
        """0부터 n-1까지의 숫자를 생성하는 제너레이터"""  
        for i in range(n):  
            yield i
    
    ## Yield가 더 이상 없으면 자동으로 StopIteration 발생
    
  4. StopIteration을 활용한 커스텀 이터레이터

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    class CustomRange:  
        """특정 범위의 숫자를 생성하는 커스텀 이터레이터"""  
        def __init__(self, start, end, step=1):  
            self.current = start  
            self.end = end  
            self.step = step
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.step > 0 and self.current >= self.end:
                raise StopIteration
            elif self.step < 0 and self.current <= self.end:
                raise StopIteration
    
            value = self.current
            self.current += self.step
            return value
    
실제 사용 예시
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def demonstrate_examples():  
    print("1. 카운트다운 이터레이터 사용:")  
    countdown = CountDown(3)  
    for num in countdown:  
        print(num) # 3, 2, 1 출력
    
    print("\n2. StopIteration 내부 동작:")
    demonstrate_stop_iteration()
    
    print("\n3. 제너레이터 사용:")
    gen = number_generator(3)
    try:
        while True:
            print(next(gen))
    except StopIteration:
        print("제너레이터 종료!")
    
    print("\n4. 커스텀 범위 이터레이터:")
    custom_range = CustomRange(1, 5)
    for num in custom_range:
        print(num)  # 1, 2, 3, 4 출력

참고 및 출처