Why Not SW CAMP 5기/수업 기록

[2월 2주차-2/12(1)]이터레이터(Iterator)와 제너레이터(Generator) 쉽게 이해하기

rubii 2025. 2. 12. 10:21

 

Python에서 이터레이터(iterator)제너레이터(generator) 는 반복(iteration)을 효율적으로 처리하는 방식입니다.


1️⃣ 이터레이터(Iterator)란?

이터레이터는 한 번에 한 개의 요소만 가져올 수 있는 객체입니다.
보통 for 문을 사용할 때 자동으로 동작하는데, 직접 만들 수도 있습니다.

이터레이터의 특징

  • 한 번 사용한 값은 사라지고 다시 돌아갈 수 없음
  • for 문에서 자동으로 next()를 호출해 다음 값을 가져옴
  • iter()로 이터레이터 객체를 만들고, next()로 값을 하나씩 꺼낼 수 있음
class MyIterator:
    def __init__(self, data):
        self.data = data
        self.position = 0  # 오타 수정 (positon → position)
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.position >= len(self.data):
            raise StopIteration
        result = self.data[self.position]  # 오타 수정 (ppsition → position)
        self.position += 1
        return result
    
if __name__ == '__main__':
    i = MyIterator([1, 2, 3])  # 리스트를 괄호가 아닌 대괄호로 감싸야 함
    for item in i:
        print(item)

🔹 리스트와 이터레이터 비교

numbers = [1, 2, 3, 4, 5]  # 리스트
iterator = iter(numbers)   # 리스트를 이터레이터로 변환

print(next(iterator))  # 1
print(next(iterator))  # 2
print(next(iterator))  # 3

📌 next(iterator) 를 호출할 때마다 한 개씩 꺼냄
📌 값이 없는데 계속 next()를 호출하면 StopIteration 오류 발생


2️⃣ 제너레이터(Generator)란?

제너레이터는 이터레이터를 더 쉽게 만들 수 있도록 도와주는 특수한 함수입니다.
return 대신 yield를 사용해 한 번에 하나씩 값을 반환합니다.

제너레이터의 특징

  • 메모리 효율적 (한 번에 한 개의 값만 생성해서 저장 X)
  • next()를 호출할 때마다 실행이 멈췄던 지점부터 다시 실행됨
  • 일반 함수처럼 def 키워드로 만들지만, yield를 사용함

🔹 제너레이터 예제

def my_generator():
    print("첫 번째 값 생성")
    yield 1
    print("두 번째 값 생성")
    yield 2
    print("세 번째 값 생성")
    yield 3

gen = my_generator()  # 제너레이터 객체 생성

print(next(gen))  # "첫 번째 값 생성" 출력 후 1 반환
print(next(gen))  # "두 번째 값 생성" 출력 후 2 반환
print(next(gen))  # "세 번째 값 생성" 출력 후 3 반환

📌 yield가 값을 반환한 후 다음 next()가 호출될 때 그 다음 코드부터 실행


 

4️⃣ 제너레이터의 실용 예제

🔹 큰 데이터 처리 (메모리 절약)

예를 들어, 1억 개의 숫자를 담는 리스트는 메모리를 많이 차지하지만, 제너레이터를 사용하면 필요할 때만 값을 생성합니다.

def count_up_to(max_num):
    num = 1
    while num <= max_num:
        yield num
        num += 1

gen = count_up_to(5)  # 1부터 5까지 생성하는 제너레이터

for num in gen:
    print(num)  # 1, 2, 3, 4, 5 출력

📌 한꺼번에 모든 숫자를 저장하지 않고 필요할 때마다 값을 생성

 

🔹 제너레이터를 활용한 '시간이 오래 걸리는 작업' 처리

import time

def longtime_job():
    print('작업 수행 중...')
    time.sleep(1)
    return '완료'

# 리스트 컴프리헨션 (즉시 실행)
list_job = [longtime_job() for i in range(5)]  # 여기서 모든 작업이 실행됨
print(list_job[0])  # '완료' (이미 수행된 결과 출력)

📌 리스트 컴프리헨션([])은 모든 작업을 즉시 실행함 → 메모리 낭비 발생


🔹 제너레이터를 활용한 '지연 실행' (Lazy Evaluation)

list_job = (longtime_job() for i in range(5))  # 리스트가 아니라 제너레이터 객체 생성

print(next(list_job))  # 첫 번째 작업 수행 (그때 실행됨)
print(next(list_job))  # 두 번째 작업 수행

📌 제너레이터 표현식(())을 사용하면 필요한 순간에만 실행됨 → 메모리 절약 가능


5️⃣ 언제 사용하면 좋을까?

 이터레이터

  • 리스트, 튜플, 딕셔너리 같은 반복 가능한 객체를 직접 제어하고 싶을 때
  • 반복할 데이터가 이미 정해져 있을 때

 제너레이터

  • 대량의 데이터를 효율적으로 처리할 때 (예: 로그 파일 읽기, 데이터 스트림 처리)
  • 메모리를 절약하고 싶을 때 (예: 무한 숫자 생성, 대용량 데이터 처리)

 

💡 결론

이터레이터 제너레이터

구현 방식 __iter__()와 __next__() 메서드를 구현 yield를 사용하여 함수 형태로 구현
사용 방식 클래스로 직접 구현해야 함 함수로 간단히 구현 가능
메모리 효율 모든 데이터를 메모리에 저장 필요할 때만 데이터를 생성 (메모리 절약)
재사용성 한 번 반복 후 재사용 불가 (새로운 객체 필요) yield를 사용하여 중단된 부분부터 실행 가능
예제 for 문에서 iter()와 next()를 직접 구현할 때 데이터 스트림, 대량 파일 읽기, API 요청 등에서 유용

💡 이터레이터는 반복 가능한 객체를 만들 때 직접 구현하는 경우 사용되며,
💡 제너레이터는 큰 데이터를 처리하거나 무거운 작업을 효율적으로 실행할 때 매우 유용합니다. 🚀