[Python 100일 챌린지] Day 64 - 이터레이터와 제너레이터 심화
이터레이터의 비밀을 파헤쳐봐요! 🔍✨
for 루프가 어떻게 동작하는지 궁금하지 않으셨나요? 파이썬의
for,map(),filter()모두 내부적으로 이터레이터를 사용해요! 오늘은 그 동작 원리를 깊이 파고들어, 나만의 이터레이터를 만들고 itertools로 강력한 데이터 처리 파이프라인을 구축할 거예요. pandas, Django QuerySet도 모두 이터레이터 패턴을 활용하고 있답니다! 😊어제 배운 제너레이터를 한 단계 더 업그레이드하는 시간이에요!
(35분 완독 ⭐⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
- Day 63: 제너레이터 기초 - 제너레이터 개념
- Phase 4: 클래스와 매직 메서드
🎯 학습 목표 1: 이터레이터 프로토콜 이해하기
한 줄 설명
이터레이터 프로토콜 = for 루프의 비밀 레시피 📜🔐
for 루프가 어떻게 동작하는지 이해하면, 어떤 객체든 for 루프에서 사용할 수 있게 만들 수 있어요!
1.1 이터레이터란?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 리스트는 이터러블(iterable)
numbers = [1, 2, 3, 4, 5]
# iter()로 이터레이터 얻기
iterator = iter(numbers)
# next()로 값 하나씩 가져오기
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# for 루프는 내부적으로 iter()와 next() 사용
for num in numbers:
print(num)
💡 실생활 비유: 책을 읽을 때, 책(이터러블)을 열면 책갈피(이터레이터)가 현재 페이지를 기억하고, 다음 페이지로 넘기는(next) 것과 같아요!
1.2 이터레이터 프로토콜
이터레이터는 두 개의 메서드를 구현해야 해요:
__iter__(): 자기 자신을 반환해요__next__(): 다음 값을 반환해요 (값이 없으면StopIteration발생)
1
2
3
4
5
6
7
8
9
10
11
12
13
# 리스트가 이터레이터를 만드는 과정
numbers = [1, 2, 3]
# 1단계: iter() 호출
iterator = iter(numbers) # numbers.__iter__()
# 2단계: next() 반복 호출
try:
while True:
item = next(iterator) # iterator.__next__()
print(item)
except StopIteration:
pass # 순회 완료
이렇게 동작해요! 🎬
for문이 시작되면iter()호출해요- 매번
next()로 값을 가져와요 StopIteration예외가 나오면 루프를 종료해요
1.3 이터러블 vs 이터레이터
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
# 이터러블(Iterable): __iter__()만 구현
class MyIterable:
def __iter__(self):
return iter([1, 2, 3])
# 이터레이터(Iterator): __iter__()와 __next__() 모두 구현
class MyIterator:
def __init__(self):
self.data = [1, 2, 3]
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 1
return value
# 사용
for item in MyIterable():
print(item)
for item in MyIterator():
print(item)
차이점 요약! 📊
- 이터러블: 반복 가능한 객체 (리스트, 튜플, 문자열 등)
- 이터레이터: 실제로 값을 하나씩 꺼내주는 객체
🎯 학습 목표 2: 커스텀 이터레이터 만들기
한 줄 설명
커스텀 이터레이터 = 나만의 for 루프 규칙 🎨📋
원하는 방식으로 데이터를 순회할 수 있는 나만의 이터레이터를 만들어요!
2.1 기본 이터레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
# 사용
for num in Countdown(5):
print(num, end=" ")
# 출력: 5 4 3 2 1
💡 핵심:
__iter__()는 자기 자신을 반환하고,__next__()는 다음 값을 반환해요!
2.2 실전: 파일 라인 이터레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FileLineIterator:
"""파일을 한 줄씩 읽는 이터레이터"""
def __init__(self, filename):
self.filename = filename
self.file = None
def __iter__(self):
self.file = open(self.filename, 'r', encoding='utf-8')
return self
def __next__(self):
line = self.file.readline()
if not line:
self.file.close()
raise StopIteration
return line.strip()
# 사용
# for line in FileLineIterator('data.txt'):
# print(line)
실무에서 이렇게 써요! 💼
- 대용량 파일을 메모리에 부담 없이 한 줄씩 처리해요
- 로그 파일 분석, CSV 처리 등에 활용해요
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
class Range:
"""range()를 흉내낸 이터레이터"""
def __init__(self, start, end, step=1):
self.start = start
self.end = end
self.step = step
self.current = start
def __iter__(self):
self.current = self.start
return self
def __next__(self):
if (self.step > 0 and self.current >= self.end) or \
(self.step < 0 and self.current <= self.end):
raise StopIteration
value = self.current
self.current += self.step
return value
# 사용
for num in Range(0, 10, 2):
print(num, end=" ")
# 출력: 0 2 4 6 8
💡 실생활 비유: 엘리베이터가 특정 층만 멈추는 것처럼, 원하는 간격으로 숫자를 생성할 수 있어요!
🎯 학습 목표 3: 제너레이터 고급 기법
한 줄 설명
제너레이터 고급 기법 = 이터레이터의 슈퍼파워 🦸♂️⚡
yield from, send() 같은 강력한 기능으로 제너레이터를 한 단계 업그레이드해요!
3.1 yield from
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def generator1():
yield 1
yield 2
def generator2():
yield 3
yield 4
# 여러 제너레이터 결합 (기본 방법)
def combined_basic():
for value in generator1():
yield value
for value in generator2():
yield value
# yield from 사용 (권장) ⭐
def combined_advanced():
yield from generator1()
yield from generator2()
print(list(combined_advanced()))
# [1, 2, 3, 4]
왜 yield from을 쓸까요? 🤔
- 코드가 훨씬 간결해요!
- 제너레이터를 쉽게 조합할 수 있어요
- 중첩 데이터 구조를 평탄화할 때 유용해요
3.2 양방향 제너레이터 (send)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def echo_generator():
"""값을 받아서 처리하는 제너레이터"""
value = None
while True:
received = yield value
if received is None:
break
value = f"받은 값: {received}"
# 사용
gen = echo_generator()
next(gen) # 제너레이터 시작
print(gen.send("Hello")) # 받은 값: Hello
print(gen.send("World")) # 받은 값: World
print(gen.send(42)) # 받은 값: 42
💡 실생활 비유: 대화하는 것처럼, 제너레이터에 값을 보내면(send) 제너레이터가 응답(yield)해줘요!
3.3 제너레이터 체이닝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def numbers(n):
"""숫자 생성"""
for i in range(n):
yield i
def square(gen):
"""제곱"""
for num in gen:
yield num ** 2
def add_one(gen):
"""1 더하기"""
for num in gen:
yield num + 1
# 체이닝 🔗
pipeline = add_one(square(numbers(5)))
print(list(pipeline))
# [1, 2, 5, 10, 17]
파이프라인의 장점! 🚰
- 각 단계를 독립적으로 테스트할 수 있어요
- 재사용 가능해요
- 메모리 효율적이에요
3.4 실전: 데이터 스트림 처리
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
def read_logs():
"""로그 파일 읽기 (시뮬레이션)"""
logs = [
"2025-01-01 10:00:00 INFO User logged in",
"2025-01-01 10:05:00 ERROR Database connection failed",
"2025-01-01 10:10:00 INFO Data saved",
"2025-01-01 10:15:00 WARNING High memory usage",
"2025-01-01 10:20:00 ERROR Timeout occurred"
]
for log in logs:
yield log
def filter_errors(logs):
"""에러만 필터링"""
for log in logs:
if 'ERROR' in log:
yield log
def extract_message(logs):
"""메시지 추출"""
for log in logs:
parts = log.split(' ', 3)
if len(parts) >= 4:
yield parts[3]
# 파이프라인 실행
error_messages = extract_message(filter_errors(read_logs()))
for message in error_messages:
print(f"❌ {message}")
# 출력:
# ❌ Database connection failed
# ❌ Timeout occurred
실무에서 이렇게 써요! 🎯
- 로그 분석, ETL 파이프라인
- 데이터 전처리 및 변환
- 실시간 스트림 처리
🎯 학습 목표 4: itertools 모듈 활용하기
한 줄 설명
itertools = 이터레이터의 스위스 아미 나이프 🔧🎩
파이썬이 제공하는 강력한 이터레이터 도구 모음이에요!
4.1 무한 이터레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import itertools
# count: 무한 카운터 ♾️
for i in itertools.count(10, 2):
print(i, end=" ")
if i >= 20:
break
# 출력: 10 12 14 16 18 20
# cycle: 무한 반복 🔄
counter = 0
for item in itertools.cycle(['A', 'B', 'C']):
print(item, end=" ")
counter += 1
if counter >= 7:
break
# 출력: A B C A B C A
# repeat: N번 반복
print(list(itertools.repeat('Hello', 3)))
# ['Hello', 'Hello', 'Hello']
무한 이터레이터의 활용! ✨
- 카운터, ID 생성기
- 순환 작업 (로드 밸런싱)
- 기본값 생성
4.2 조합 이터레이터
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
import itertools
# chain: 여러 이터러블 연결 🔗
list1 = [1, 2, 3]
list2 = [4, 5, 6]
print(list(itertools.chain(list1, list2)))
# [1, 2, 3, 4, 5, 6]
# zip_longest: 가장 긴 것에 맞춰 zip
list1 = [1, 2, 3]
list2 = ['A', 'B']
print(list(itertools.zip_longest(list1, list2, fillvalue='X')))
# [(1, 'A'), (2, 'B'), (3, 'X')]
# product: 카르테시안 곱 (모든 조합)
colors = ['red', 'blue']
sizes = ['S', 'M', 'L']
print(list(itertools.product(colors, sizes)))
# [('red', 'S'), ('red', 'M'), ('red', 'L'),
# ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]
# permutations: 순열 (순서 O)
print(list(itertools.permutations([1, 2, 3], 2)))
# [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
# combinations: 조합 (순서 X)
print(list(itertools.combinations([1, 2, 3], 2)))
# [(1, 2), (1, 3), (2, 3)]
💡 실생활 비유: 옷을 고를 때, product는 “색상 × 사이즈” 모든 조합, combinations는 “3벌 중 2벌 고르기”예요!
4.3 필터링 이터레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import itertools
# takewhile: 조건이 True인 동안만 🎯
numbers = [1, 2, 3, 4, 5, 1, 2]
print(list(itertools.takewhile(lambda x: x < 4, numbers)))
# [1, 2, 3]
# dropwhile: 조건이 True인 동안 건너뛰기
print(list(itertools.dropwhile(lambda x: x < 4, numbers)))
# [4, 5, 1, 2]
# filterfalse: filter의 반대
print(list(itertools.filterfalse(lambda x: x % 2 == 0, range(10))))
# [1, 3, 5, 7, 9]
# islice: 슬라이싱 ✂️
print(list(itertools.islice(range(10), 2, 8, 2)))
# [2, 4, 6]
4.4 그룹화 이터레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import itertools
# groupby: 연속된 같은 값 그룹화
data = [1, 1, 2, 2, 2, 3, 3, 1, 1]
for key, group in itertools.groupby(data):
print(f"{key}: {list(group)}")
# 출력:
# 1: [1, 1]
# 2: [2, 2, 2]
# 3: [3, 3]
# 1: [1, 1]
# 실전: 문자열 압축
def compress(s):
"""연속된 문자 압축"""
result = []
for char, group in itertools.groupby(s):
count = len(list(group))
result.append(f"{char}{count if count > 1 else ''}")
return ''.join(result)
print(compress("aaabbccccd")) # a3b2c4d
실무에서 이렇게 써요! 💼
- 데이터 압축 알고리즘
- 연속된 이벤트 그룹화
- 시계열 데이터 집계
4.5 실전 예제: 배치 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import itertools
def chunked(iterable, n):
"""이터러블을 n개씩 묶음"""
iterator = iter(iterable)
while True:
chunk = list(itertools.islice(iterator, n))
if not chunk:
break
yield chunk
# 사용
data = range(1, 11)
for batch in chunked(data, 3):
print(batch)
# 출력:
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10]
실무 활용! 🎯
- 대량 DB insert (배치 처리)
- 머신러닝 미니배치
- API 요청 분할
4.6 실전 예제: 윈도우 슬라이딩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from collections import deque
import itertools
def sliding_window(iterable, n):
"""슬라이딩 윈도우"""
iterator = iter(iterable)
window = deque(itertools.islice(iterator, n), maxlen=n)
if len(window) == n:
yield tuple(window)
for item in iterator:
window.append(item)
yield tuple(window)
# 사용
for window in sliding_window([1, 2, 3, 4, 5], 3):
print(window)
# 출력:
# (1, 2, 3)
# (2, 3, 4)
# (3, 4, 5)
실무 활용! 💡
- 이동 평균 계산
- 시계열 분석
- 패턴 인식
💡 오늘의 핵심 요약
- 이터레이터 프로토콜:
__iter__(): 자기 자신 반환해요__next__(): 다음 값 반환해요StopIteration: 순회 종료 신호예요
- 제너레이터 고급 기법:
yield from: 다른 제너레이터 위임해요send(): 양방향 통신 가능해요- 체이닝: 파이프라인 구성해요
- itertools 모듈:
- 무한 이터레이터:
count,cycle,repeat - 조합:
chain,product,permutations,combinations - 필터링:
takewhile,dropwhile,filterfalse - 그룹화:
groupby,islice
- 무한 이터레이터:
- 활용 사례:
- 🗂️ 대용량 데이터 처리
- 🚰 데이터 파이프라인
- 📊 배치 처리
- 🔄 순환 작업
🧪 연습 문제
문제 1: 윈도우 슬라이딩 이터레이터
연속된 N개 요소의 윈도우를 생성하는 이터레이터를 작성하세요.
1
2
3
4
5
6
7
8
9
10
11
def sliding_window(iterable, n):
# 여기에 코드 작성
pass
# 사용
for window in sliding_window([1, 2, 3, 4, 5], 3):
print(window)
# 출력:
# (1, 2, 3)
# (2, 3, 4)
# (3, 4, 5)
💡 힌트
단계별 힌트:
collections.deque를 사용하여 고정 크기 윈도우를 만들어요itertools.islice로 첫 N개 항목을 채워요- 나머지 항목들을 하나씩 추가하면서 yield해요
핵심 키워드: deque(maxlen=n), itertools.islice, yield tuple(window)
✅ 정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import itertools
from collections import deque
def sliding_window(iterable, n):
"""윈도우 슬라이딩 이터레이터"""
iterator = iter(iterable)
window = deque(itertools.islice(iterator, n), maxlen=n)
if len(window) == n:
yield tuple(window)
for item in iterator:
window.append(item)
yield tuple(window)
# 테스트
for window in sliding_window([1, 2, 3, 4, 5], 3):
print(window)
# 문자열에도 사용 가능
for window in sliding_window("ABCDE", 3):
print(''.join(window))
# 출력: ABC, BCD, CDE
어떻게 동작할까요?
deque(maxlen=n): 최대 크기가 n인 큐를 만들어요- 새 항목이 추가되면 오래된 항목이 자동으로 제거돼요
- 매번 현재 윈도우를 튜플로 반환해요
문제 2: 피보나치 이터레이터
무한 피보나치 수열을 생성하는 이터레이터를 클래스로 작성하세요.
💡 힌트
단계별 힌트:
__init__에서 초기값 a=0, b=1을 설정해요__iter__는 self를 반환해요__next__에서 다음 피보나치 수를 계산하고 반환해요
핵심 키워드: __iter__, __next__, a, b = b, a + b
✅ 정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Fibonacci:
"""무한 피보나치 수열 이터레이터"""
def __init__(self):
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
result = self.a
self.a, self.b = self.b, self.a + self.b
return result
# 테스트
fib = Fibonacci()
for i, num in enumerate(fib):
print(num, end=" ")
if i >= 9:
break
# 출력: 0 1 1 2 3 5 8 13 21 34
왜 클래스를 쓸까요?
- 상태(a, b)를 인스턴스 변수로 저장해요
- 여러 개의 독립적인 피보나치 이터레이터를 만들 수 있어요
📝 오늘 배운 내용 정리
- 이터레이터 프로토콜:
__iter__와__next__로 for 루프의 동작 원리를 이해했어요 - 커스텀 이터레이터: 나만의 순회 규칙을 가진 이터레이터를 만들 수 있어요
- 제너레이터 고급 기법:
yield from,send()로 더 강력한 제너레이터를 만들어요 - itertools 모듈: 실무에서 자주 쓰는 이터레이터 도구들을 마스터했어요
🔗 관련 자료
📚 이전 학습
Day 63: 제너레이터 기초 ⭐⭐⭐
어제는 yield 키워드로 메모리 효율적인 제너레이터를 만드는 방법을 배웠어요!
📚 다음 학습
Day 65: 컨텍스트 매니저 ⭐⭐⭐
내일은 with 문의 동작 원리와 리소스를 안전하게 관리하는 컨텍스트 매니저를 배워요!
“복잡한 것도 원리를 알면 간단해져요!” 🚀
Day 64/100 Phase 7: 고급 파이썬 개념 #100DaysOfPython
