Python (5/16) - 2

김소은·2025년 5월 22일

이터레이터(Iterator)제너레이터(Generator) 는 파이썬에서 순회 가능한(iterable) 객체를 다룰 때 중요한 개념입니다.


📚 이터레이터(Iterator)

  1. 정의

    • __iter__() 메서드와 __next__() 메서드를 구현한 객체.
    • __iter__() 는 자신을 반환하고, __next__() 는 다음 값을 반환하다가 더 이상 값이 없으면 StopIteration 예외를 발생시킵니다.
  2. 사용 예시

     class Counter:
         def __init__(self, low, high):
             self.current = low
             self.high = high

         def __iter__(self):
             return self

         def __next__(self):
             if self.current > self.high:
                 raise StopIteration
             val = self.current
             self.current += 1
             return val

     for num in Counter(1, 3):
         print(num)
     # 출력:
     # 1
     # 2
     # 3
  1. 특징

    • 메모리에 한 번에 모든 값을 올리지 않고도 순회 가능
    • 커스텀 순회 로직을 직접 정의할 수 있음

📚 제너레이터(Generator)

  1. 정의

    • 함수 내에 yield 키워드를 사용하여 제너레이터 객체를 반환하는 특별한 형태의 이터러블
    • 호출 시 함수 전체가 실행되지 않고, yield 지점까지 실행된 뒤 값을 하나씩 반환하며 상태를 유지
  2. 사용 예시

  def count_up_to(n):
      current = 1
      while current <= n:
          yield current
          current += 1

  for num in count_up_to(3):
      print(num)
  # 출력:
  # 1
  # 2
  # 3
  1. 장점

    • 이터레이터 클래스를 직접 정의할 필요 없이 간편하게 작성
    • 내부 상태(로컬 변수들)를 자동으로 기억하기 때문에 코드가 간결
    • return 대신 StopIteration이 자동 발생

Iterator vs Generator 비교

구분Iterator 클래스Generator 함수
정의 방식class__iter__, __next__ 구현def 함수에 yield 사용
코드 복잡도메서드 여러 개 작성 필요한 줄 yield로 간편
상태 유지직접 변수로 관리파이썬이 자동으로 상태 보존
예외 처리StopIteration 직접 발생return 또는 함수 종료 시 자동 발생

활용 시점

  • Iterator: 순회 로직이 복잡하거나, 추가 메서드(reset() 등)를 함께 제공하고 싶을 때
  • Generator: 간단히 “순차적으로 값을 뽑아내는” 기능만 필요할 때

핵심 요약

  • 이터레이터는 순회 프로토콜(__iter__, __next__)을 구현한 객체
  • 제너레이터yield를 이용해 간단히 이터레이터를 생성하는 함수
  • 제너레이터를 쓰면 코드가 더 짧고, 상태 관리도 자동으로 이루어집니다.

📚 map() & filter()의 활용

파이썬의 map()filter() 함수는 둘 다 지연 평가(lazy evaluation) 방식으로 동작하는 이터러블(순회 가능한) 객체를 반환하기 때문에, 이터레이터제너레이터와 함께 쓰면 메모리를 절약하면서도 필요한 시점에만 계산을 수행할 수 있습니다.

1. map()

  • 용도: 입력 이터러블의 각 요소에 동일한 함수를 적용하여 새 이터러블을 생성
  • 반환값: map 객체 (이터레이터)
# 예시 1: 리스트에 적용
nums = [1, 2, 3, 4]
doubled = map(lambda x: x * 2, nums)
print(type(doubled))       # <class 'map'>
print(list(doubled))       # [2, 4, 6, 8]

# 예시 2: 제너레이터와 함께 사용
def count_up_to(n):
    for i in range(1, n+1):
        yield i

gen = count_up_to(5)                     # 1,2,3,4,5 생성
mapped = map(lambda x: x**2, gen)       # 제곱 값만 차례대로
for val in mapped:
    print(val)  
# 출력: 1 4 9 16 25
  • 제너레이터 gen 자체도 이터레이터이기 때문에, map()이 내부에서 __iter__()__next__() 를 호출하며 한 번에 한 요소씩 처리합니다.

2. filter()

  • 용도: 입력 이터러블의 각 요소에 조건 함수를 적용하고, **참(True)**인 요소만 걸러낸 새 이터러블 생성
  • 반환값: filter 객체 (이터레이터)
# 예시 1: 리스트에서 짝수만 걸러내기
nums = [1, 2, 3, 4, 5, 6]
evens = filter(lambda x: x % 2 == 0, nums)
print(list(evens))        # [2, 4, 6]

# 예시 2: 제너레이터와 함께 사용
def infinite_numbers():
    i = 1
    while True:
        yield i
        i += 1

gen_inf = infinite_numbers()
filtered = filter(lambda x: x % 3 == 0, gen_inf)	# 여기서 filtered는 이터레이터 객체. list가 아님.

# 무한 제너레이터라도 filter 로직으로 조건 만족 시점만 소비
import itertools
for val in itertools.islice(filtered, 5):
    print(val)
# 출력: 3 6 9 12 15
  • 무한 이터레이터(제너레이터)라도 filter()로 원하는 개수만큼만 꺼내 쓰면 무한 루프에 빠지지 않고 필요한 만큼만 처리할 수 있습니다.

3. map()·filter() 연쇄 사용

def ints():
    yield from range(1, 11)

# 1) 1~10 → 짝수만 걸러 → 제곱 → 출력
# pipeline = (x**2 for x in ints() if x % 2 == 0)
pipeline = map(lambda x: x**2,
               filter(lambda x: x % 2 == 0,
                      ints()))

print(list(pipeline))	# print pipeline(iterator object) as a form of list
# [4, 16, 36, 64, 100]
  • 내부적으로

    1. ints() 제너레이터가 값을 하나씩 꺼내고
    2. filter()가 짝수만 통과시키며
    3. map()이 제곱을 수행
  • 모두 지연 평가 되므로, 중간 결과가 메모리에 쌓이지 않습니다.


4. 주의사항

  1. 재사용 불가

    • 한 번 순회하면 map/filter 객체는 소진되어 버리므로, 재사용하려면 다시 생성해야 합니다.
  2. 가독성

    • 여러 단계 연쇄가 복잡해지면 가독성이 떨어질 수 있습니다. 이럴 땐 제너레이터 표현식 또는 리스트 컴프리헨션(작은 데이터셋인 경우)으로 대체를 고려하세요.
# 같은 동작을 제너레이터 표현식으로
pipeline = (x**2 for x in ints() if x % 2 == 0)

요약

  • map()filter()이터레이터/제너레이터와 결합해 메모리 효율지연 평가 이점을 제공
  • 무한 시퀀스 처리나 대용량 데이터 스트리밍에 특히 유용
  • 코드가 복잡해지면 제너레이터 표현식이나 comprehension으로 가독성을 챙기는 것이 좋습니다.
profile
개발자

0개의 댓글