파이썬 이터레이터(Iterator)와 제너레이터(Generator)
파이썬 이터레이터(Iterator)와 제너레이터(Generator)의 차이점을 살펴봅니다.
2024. 10. 22 최초작성
이터러블과 이터레이터
제너레이터
이터러블과 이터레이터
이터러블(Iterable)은 반복 가능한 파이썬 객체로 for 루프를 사용하여 객체의 원소를 순회하여 접근할 수 있습니다. 이터러블의 예로는 리스트, 집합, 튜플, 딕셔너리, 문자열 등이 있습니다. iter() 함수를 통해 이터레이터를 반환합니다.
이터레이터(iterator)는 리스트, 튜플 등의 이터러블 객체에 iter() 함수를 적용하여 얻을 수 있는 객체입니다. 이 객체는 __next__() 메서드를 통해 객체의 요소를 순차적으로 접근할 수 있습니다. 한 번에 하나의 요소만 반환하며, 모든 요소를 순회하면 StopIteration 예외를 발생시킵니다. 메모리를 효율적으로 사용할 수 있습니다.
# 이터러블 중 하나인 리스트입니다. for 문을 사용해서 요소를 순회하여 접근할 수 있습니다. 이터러블은 여러번 순회가능합니다. # 이터러블은 전체 시퀀스를 메모리에 저장하고 순차적으로 하나씩 접근하기때문에 매번 처음부터 순회를 시작합니다. my_list = [1, 2, 3, 4, 5] # iter 함수를 사용하여 이터러블인 리스트에서 이터레이터 객체를 생성합니다. my_iterator = iter(my_list) # 이터레이터를 next 함수에 사용하여 리스트의 요소에 순차적으로 접근합니다. print(next(my_iterator)) # 출력: 1 print(next(my_iterator)) # 출력: 2 print(next(my_iterator)) # 출력: 3 # for 루프로 나머지 요소 순회합니다. 이터레이터는 현재 위치만 기억하기 때문에 앞에서 순회한 3 다음인 4부터 순회하게 됩니다. for item in my_iterator: print(item) # 출력: 4, 5 # 이터레이터는 한 번 순회하면 끝납니다. 현재 모든 요소를 순회한 후라서 다시 `next()`를 호출하면 더 이상 요소가 없어서 `StopIteration` 예외가 발생합니다. try: print(next(my_iterator)) except StopIteration: print("이터레이터의 모든 요소를 순회했습니다.") # 이터러블(리스트)은 변하지 않으므로 언제든 새로운 이터레이터를 생성할 수 있습니다. print(my_list) # 출력: [1, 2, 3, 4, 5] # 새로운 이터레이터를 생성합니다. new_iterator = iter(my_list) print(next(new_iterator)) # 출력: 1 (다시 처음부터 요소를 순회를 할 수 있습니다. ) |
이터러블은 한 번에 하나씩 요소를 반환할 수 있는 객체로, for 문을 사용하여 요소를 순회할 수 있습니다. 이터러블의 예로는 리스트, 튜플, 집합, 문자열, 딕셔너리가 있습니다. 문자열은 문자의 반복을 생성할 수 있고 딕셔너리의 키는 반복될 수 있습니다. 경험상 for-루프에서 반복할 수 있는 모든 객체는 이터러블로 간주합니다.
파이썬은 for 문을 사용하여 이터러블 객체를 반복하려고 할 때마다 이터레이터 객체를 자동으로 생성합니다.
# 리스트 객체를 생성합니다. list_instance = [1, 2, 3, 4] # for 문을 사용하여 리스트를 순회합니다. for iterator in list_instance: print(iterator) |
1
2
3
4
이터레이터를 사용하여 왼쪽에서 오른쪽 방향으로만 값을 순차적으로 접근할 수 있습니다. 반대방향으로 접근하는 방법은 제공되지 않습니다.
동일한 이터러블 객체를 기반으로 여러 개의 이터레이터를 정의할 수 있습니다. 각 이터레이터는 각자의 진행 상태를 유지합니다. 한 이터레이터가 첫번째 요소에 접근하는 동안 다른 이터레이터는 마지막 요소에 접근할 수 있습니다.
list_instance = [1, 2, 3, 4] iterator_a = iter(list_instance) iterator_b = iter(list_instance) print(f"A: {next(iterator_a)}") print(f"A: {next(iterator_a)}") print(f"A: {next(iterator_a)}") print(f"A: {next(iterator_a)}") print(f"B: {next(iterator_b)}") |
A: 1
A: 2
A: 3
A: 4
B: 1
iterator_a가 마지막 요소를 출력한 사이 iterator_b는 첫번째 요소를 출력한다는 점에 주목하세요.
이터레이터가 생성되면 요소가 요청될 때까지 반환되지 않습니다. 다시 말해, 명시적으로 next(iter(list_instance))를 요청해야만 반환됩니다.
그러나 이터레이터 객체에서 내장된 이터러블 데이터 구조 컨테이너(예: 리스트(), 세트(), 튜플())를 호출하여 이터레이터가 모든 요소를 한 번에 생성하도록 하면 이터레이터의 모든 값을 한 번에 추출할 수 있습니다.
# instantiate iterable list_instance = [1, 2, 3, 4] # produce an iterator from an iterable iterator = iter(list_instance) print(list(iterator)) |
[1, 2, 3, 4]
하지만 이터러블을 구성하는 요소가 많은 경우에는 결과를 얻기까지 오래걸리기 때문에 이 방법을 권장하지 않습니다.
제너레이터(Generator)
제너레이터는 함수의 특별한 형태로 일반 함수와 달리 값을 즉시 반환하지 않습니다. 대신 여러 값의 시퀀스(순서가 있는 일련의 값들)를 생성합니다. 이 값들은 한 번에 하나씩, 필요할 때마다 생성됩니다. `yield` 키워드를 사용하여 값을 하나씩 반환합니다. 호출되면 제너레이터 객체(이터레이터의 한 종류)를 반환합니다.
제너레이터는 모든 값을 한 번에 메모리에 저장하지 않고, 필요할 때마다 값을 생성하기 때문에 메모리를 효율적으로 사용합니다. 실제로 값이 필요할 때까지 계산을 미루기 때문에 지연 평가(Lazy Evaluation)라고 합니다. 이터레이터 프로토콜을 따르기 때문에 next() 함수로 다음 값을 얻을 수 있습니다.
1부터 n까지의 제곱수를 생성하는 제너레이터 함수를 만들어 보겠습니다.
# 제너레이터 함수입니다. `yield` 키워드를 사용하여 값을 하나씩 반환합니다. def square_numbers(n): for i in range(1, n + 1): yield i * i # 제너레이터 객체를 생성합니다. 이 시점에서는 아직 어떤 계산도 수행되지 않습니다. squares = square_numbers(5) # next(squares)를 호출할 때마다 다음 제곱수가 계산되어 반환됩니다. print(next(squares)) # 출력: 1 print(next(squares)) # 출력: 4 print(next(squares)) # 출력: 9 # for 루프로 나머지 값 순회합니다. for square in squares: print(square) # 출력: 16, 25 # 모든 값을 소진한 후 다시 next()를 호출하면 StopIteration 예외 발생합니다. try: print(next(squares)) except StopIteration: print("제너레이터의 모든 값을 소진했습니다.") # 새로운 제너레이터 객체를 생성하면 처음부터 다시 시작할 수 있습니다. squares_again = square_numbers(3) print(list(squares_again)) # 출력: [1, 4, 9] |
출처
https://www.datacamp.com/tutorial/python-iterators-generators-tutorial