개요
대용량 데이터를 다룰 때 필수적인 이터레이터와 제너레이터에 대해 쉽고 명확하게 정리해 보려고 합니다. 이 두 가지를 제대로 이해하면 코드를 더 효율적이고 간결하게 작성할 수 있습니다.
이터레이터(Iterator)란?
개념 : 데이터를 순서대로 하나씩 꺼내는 장치
이터레이터(Iterator)는 반복하는 장치라는 의미 그대로, 데이터를 순서대로 하나씩 꺼낼 수 있는 객체입니다. 리스트나 튜플처럼 모든 데이터를 메모리에 한꺼번에 올려놓는 방식이 아니라 필요할 때마다 값을 하나씩 가져오는 똑똑한 객체라고 볼 수 있습니다.
핵심 용어 정리
- 이터러블(Iterable) : for 루프에 사용할 수 있는 객체. __iter__() 메서드를 가지고 있습니다. 리스트, 튜플, 문자열 등이 대표적인 이터러블입니다.
- 던더(Dunder) 메서드: __로 시작하고 끝나는 특별한 메서드(ex. __iter__, __next__) 사용자가 직접 호출하기보다는 파이썬 내부 동작에 의해 자동으로 호출됩니다.
- StopIteration 예외 : 이터레이터가 더 이상 반환할 값이 없을 때 발생하는 예외입니다. next() 함수를 호출했을 때 이 예외가 발생하면 반복이 끝났다는 신호입니다.
왜 필요할까
이터레이터의 가장 큰 장점은 메모리 효율성입니다.
- 메모리 절약 : 1억 개의 데이터를 리스트로 만들면 메모리가 부족할 수 있지만, 이터레이터는 값을 하나씩 생성 하므로 메모리를 훨씬 적게 사용합니다.
- 일관된 인터페이스 : for 루프, while 루프, next() 함수 등 다양한 파이썬 반복 구문이 이터레이터 프로토콜을 기반으로 작동하기 때문에, 모든 자료형을 통일된 방식으로 다룰 수 있습니다.
- 무한 데이터 처리 : 끝이 없는 데이터 스트림(예: 실시간 로그, 무한 수열)도 문제 없이 처리할 수 있습니다.
이터레이터 사용하기
1. 내장 이터레이터 사용하기
파이썬의 기본 자료형들은 이미 이터레이터 프로토콜을 따릅니다. iter() 함수로 이터레이터 객체를 만들고, next() 함수로 값을 하나씩 꺼낼 수 있습니다.
numbers = [10, 20, 30]
it = iter(numbers)
print(next(it)) # 출력: 10
print(next(it)) # 출력: 20
print(next(it)) # 출력: 30
# next(it)를 다시 호출하면 StopIteration 예외 발생
2. 사용자 정의 이터레이터 만들기
__iter__()와 __next__() 메서드를 구현하여 나만의 이터레이터를 만들 수 있습니다.
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
countdown = CountDown(3)
iterator = iter(countdown)
print(next(iterator)) # 출력: 3
print(next(iterator)) # 출력: 2
print(next(iterator)) # 출력: 1
제너레이터(Generator)는 이터레이터와 무엇이 다를까?
개념 : 이터레이터를 자동으로 만들어주는 문법
제너레이터(Generator)는 이터레이터를 더욱 쉽고 간결하게 만들 수 있는 특별한 함수나 표현식입니다. 이터레이터 클래스를 직접 만들 필요 없이 yield 키워드만 사용하면 자동으로 이터레이터처럼 동작하는 객체를 생성해 줍니다.
핵심 특징
- yield 키워드 : return과 비슷하지만, 값을 반환한 후 함수의 실행 상태를 그대로 일시정지 시킵니다. next()가 다시 호출되면 멈췄던 시점부터 실행을 재개합니다.
- 지연 평가(Lazy Evaluation) : 모든 결과를 미리 계산하지 않고 값이 필요할 때만 계산을 수행합니다. 덕분에 메모리를 절약하고 불필요한 연산을 피할 수 있습니다.
왜 필요할까?
제너레이터는 이터레이터의 장점을 그대로 가져오면서, 코드의 간결성을 극대화합니다.
- 메모리 효율성 : 이터레이터와 동일하게 대용량 데이터를 처리할 때 메모리를 절약합니다.
- 간결한 문법 : __iter__와 __next__ 메서드를 직접 구현할 필요가 없어 코드가 훨씬 짧고 가독성이 좋습니다.
제너레이터 사용하기
1. 제너레이터 함수
함수 내부에 yield 키워드를 사용하여 제너레이터를 만듭니다.
def countdown_generator(n):
while n > 0:
yield n
n -= 1
gen = countdown_generator(3)
print(next(gen)) # 출력: 3
print(next(gen)) # 출력: 2
print(next(gen)) # 출력: 1
2. 제너레이터 표현식
리스트 컴프리헨션과 비슷하지만, 대괄호[] 대신 소괄호()를 사용합니다.
squares = (x * x for x in range(5))
print(next(squares)) # 출력: 0
print(next(squares)) # 출력: 1
이터레이터 VS 제너레이터 : 무엇을 사용해야 할까?
구분 | 이터레이터(클래스) | 제너레이터(함수, 표현식) |
생성 방법 | __iter__() 와 __next() 메서드를 가진 클래스 | yield를 사용하는 함수 또는 () 표현식 |
메모리 효율성 | O(동일) | O(동일) |
코드 간결성 | 상대적으로 복잡 | 매우 간결하고 직관적 |
주요 사용처 | 복잡한 상태를 관리하거나 재사용 가능한 반복 로직이 필요할 때 | 대부분의 경우 특히 간단한 반복 로직이나 대용량 데이터 처리 시 |
대부분의 경우 제너레이터를 사용하는 것이 훨씬 효율적입니다. 코드가 간결하고 작성하기 쉽기 때문입니다. 하지만, 반복을 위해 여러 상태를 복잡하게 관리해야 하거나 객체를 재사용해야 하는 경우라면 이터레이터 클래스를 직접 정의하는 것이 더 적합할 수 있습니다.
결론
이터레이터와 제너레이터는 파이썬의 핵심 철학인 메모리 효율성과 코드 간결성을 잘 보여주는 개념입니다.무턱대고 리스트에 모든 데이터를 담는 대신, 이 둘을 활용하면 메모리 문제를 해결하고 코드를 더 우아하게 만들 수 있습니다. 오늘 배운 내용을 잘 기억해두셨다가 필요한 순간에 멋지게 활용하시길 바랍니다.
'Dev > Python' 카테고리의 다른 글
[Python] Python 예외 처리 (0) | 2025.09.26 |
---|---|
[Python] 인자 규약 (0) | 2025.09.25 |
[Python] 함수 시그니처 (0) | 2025.09.24 |
[Python] 모듈(Module) & 패키지(Package) (0) | 2025.09.23 |
[Python] 타입 힌트 (0) | 2025.09.22 |