Post

이터레이터와 제너레이터

이터레이터 (Iterator)

이터레이터는 파이썬의 반복 가능한 객체(예: 리스트, 튜플, 딕셔너리 등)에서 요소를 하나씩 차례로 접근할 수 있게 해주는 객체이다. 이터레이터의 가장 큰 특징은 모든 요소를 메모리에 미리 적재하지 않고, 반복문 내에서 요소를 하나씩 처리할 수 있다는 점이다. 이는 큰 데이터를 다룰 때 메모리 사용을 효율적으로 만들어 준다.

단순 반복문보다 효율적인 이유 ❗️

큰 데이터 셋을 처리할 때, 프로그램은 처리해야 할 데이터의 전체 크기에 비례하는 메모리를 소비한다. 데이터가 매우 크면, 이는 메모리 용량을 초과할 수 있으며, 이 때문에 프로그램이 느려지거나 실패할 수 있다. 이터레이터는 현재 처리 중인 요소만을 메모리에 유지한다. 따라서 전체 데이터를 메모리에 적재할 필요가 없어 메모리 사용량이 줄어든다.

사용 방법

  • iter(): 반복 가능한 객체로부터 이터레이터를 생성한다.
  • next(): 이터레이터에서 다음 요소를 가져온다. 더 이상 가져올 요소가 없을 때 StopIteration 예외를 발생시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 고객 이름이 담긴 리스트
customers = ['Alice', 'Bob', 'Charlie', 'Diana']

# 리스트로부터 이터레이터 생성
customer_iterator = iter(customers)

# 모든 요소를 순회하며 출력
while True:
    try:
        # 다음 요소를 가져옴
        customer = next(customer_iterator)
        print(customer)
    except StopIteration:
        # 더 이상 요소가 없으면 반복 종료
        break

제너레이터

제너레이터(generator)는 파이썬에서 반복자(iterator)를 생성하는 간단하고 강력한 도구이다. 함수 안에서 yield 키워드를 사용하여 작성되며, yield를 만날 때마다 함수의 상태가 일시 중지되고 값을 반환한다. 함수가 다시 호출되면, 중단된 지점부터 실행이 계속된다.

특징

  • 메모리 효율성: 제너레이터는 모든 값을 메모리에 미리 적재하지 않고, 반복될 때마다 하나씩 값을 생성한다. 이는 큰 데이터 시퀀스를 작업할 때 메모리 사용량을 크게 줄일 수 있다.

  • 게으른 실행(Lazy Evaluation): 제너레이터는 값이 필요할 때까지 계산을 미룬다.

  • 상태 유지: 제너레이터는 마지막으로 실행된 위치와 변수의 상태를 유지한다. 따라서 함수의 로컬 변수 상태를 매 호출마다 유지하며, 다음 값이 필요할 때 같은 상태에서 실행을 재개할 수 있습니다.

사용 방법

제너레이터를 사용하는 기본적인 방법은 함수 내에서 yield 키워드를 사용하는 것이다. yield는 함수를 일시 중지시키고 값을 반환하는 데 사용된다. 함수가 다시 호출될 때, yield 다음의 문장부터 실행이 계속된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
def simple_generator():
    yield 1
    yield 2
    yield 3

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

# 제너레이터에서 값을 하나씩 가져옴
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
# 다음 next(gen) 호출 시 StopIteration 예외 발생
  • 제너레이터 표현식: 리스트 컴프리헨션과 유사한 문법으로 제너레이터 표현식(generator expression)을 사용할 수 있다. 제너레이터 표현식은 괄호 안에 리스트 컴프리헨션과 유사한 구문을 사용하여 정의된다. 이 방법은 간단한 제너레이터를 빠르게 생성할 때 유용하다.
1
2
3
4
5
6
# 제너레이터 표현식 예제
gen_expression = (x**2 for x in range(10))

# 제너레이터에서 값을 하나씩 가져옴
for value in gen_expression:
    print(value)

이터레이터 vs 제너레이터

언뜻 보면 유사한 동작을 수행하는 것 같아 굳이 나눌 필요가 없어보이는데, 차이점은 무엇일까? 🧐

이터레이터(iterator)와 제너레이터(generator)는 유사한 동작을 수행하는 것처럼 보일 수 있지만, 개념적으로 및 구현 방식에 있어서 몇 가지 중요한 차이점이 있다.

  • 이터레이터(Iterator)
    • 일반적인 개념: 이터레이터는 파이썬의 반복 가능한 객체(예: 리스트, 튜플, 딕셔너리 등)를 순회할 수 있는 객체이다. 이터레이터는 iter() 함수로 생성되며, next() 함수를 사용해 연속된 요소에 접근할 수 있다.
    • 동작 원리: 이터레이터는 iter()와 next() 메서드를 구현하는 모든 객체이다. iter() 메서드는 이터레이터 객체 자체를 반환하고, next() 메서드는 컬렉션의 다음 요소를 반환한다.
  • 제너레이터(Generator)
    • 특별한 유형의 이터레이터: 제너레이터는 이터레이터를 편리하게 생성하기 위한 도구이다. 함수 내부에서 yield 키워드를 사용하여 값을 반환함으로써 구현된다.
    • 동작 원리: yield 키워드를 만날 때마다 함수의 실행이 일시 중지되고, 그 시점의 함수 컨텍스트(지역 변수, 실행 상태 등)가 저장된다. 함수가 다시 호출될 때는 저장된 상태에서 실행을 재개한다.

요약하자면, 제너레이터는 이터레이터의 특별한 형태로, 이터레이터를 보다 쉽게 구현할 수 있게 해주는 도구이다. 둘 다 파이썬에서 반복 작업을 수행하는 데 사용되지만, 제너레이터는 특히 메모리 사용 최적화와 Lazy Loading에 유용하다. ❗️

그럼 모든 반복문은 제너레이터를 사용하면 되겠네? 🫢

제너레이터는 특정 상황에서 매우 유용하지만, 모든 경우에 만능 해결책이 되지는 않는다.

제너레이터를 사용했을 때 비효율적일 수 있는 상황 중 하나는 데이터에 여러 번 액세스해야 하거나, 특정 요소에 임의로 접근해야 하는 경우이다. 이런 상황에서는 제너레이터가 아닌 리스트 또는 다른 자료구조를 사용하는 것이 더 적합할 수 있다.

  • 데이터에 여러 번 액세스하는 경우: 여러 조건을 사용하여 데이터 집합에서 요소를 필터링해야 할 때 제너레이터를 사용하면, 한 번 순회한 후 다시 사용할 수 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 제너레이터 함수 정의
def data_generator():
    for i in range(1, 6):
        yield i

# 제너레이터 생성
gen = data_generator()

# 첫 번째 조건으로 데이터 필터링 (예: 짝수)
even_numbers = [x for x in gen if x % 2 == 0]
print("짝수:", even_numbers)

# 같은 제너레이터를 다시 사용하여 두 번째 조건으로 데이터 필터링 시도 (예: 3 이상)
# 이 시도는 실패함. 제너레이터는 이미 소진되었음.
greater_than_two = [x for x in gen if x > 2]
print("3 이상인 숫자:", greater_than_two)  # 출력: []
  • 특정 요소에 임의로 접근해야 하는 경우: 특정 인덱스의 요소에 직접 접근하려 할 때, 제너레이터는 이를 지원하지 않음. 반면, 리스트 같은 자료구조는 인덱스를 통한 직접 접근을 쉽게 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 리스트 사용
data_list = [1, 2, 3, 4, 5]

# 특정 인덱스의 요소에 직접 접근 (예: 세 번째 요소)
third_element = data_list[2]  # 인덱스는 0부터 시작하므로, 2는 세 번째 요소를 의미
print("리스트에서 세 번째 요소:", third_element)

# 제너레이터 사용 시도
gen = (x for x in range(1, 6))

# 제너레이터에서 특정 인덱스의 요소에 직접 접근 시도
# 이는 제너레이터의 특성상 불가능하므로, 대안적 접근 방식이 필요함.
# 예를 들어, 특정 요소까지 모두 소모하는 방법이 있으나, 이는 비효율적임.
third_element_gen = None
for i, element in enumerate(gen):
    if i == 2:  # 세 번째 요소를 찾으면
        third_element_gen = element
        break
print("제너레이터에서 세 번째 요소:", third_element_gen)
This post is licensed under CC BY 4.0 by the author.