-
Iterators와 Generators개발 공부의 시작/python 2024. 1. 11. 21:12728x90
잘 이해를 못한거 같아서 나름대로 정리해본다
파이썬에서 다음과 같은 형태로 반복문을 많이 사용한다.
for i in [2, 3, 5, 7, 11, 13]: print(i)파이썬의 for in 구문은 다른 언어의 반복문보다 간결하고 명확하다는 장점을 갖는다.
하지만 여기서 파이썬에서 저 for 구문을 처리할 때 어떤 일이 일어날까?
그리고 반복문을 통해 순회되거나 열거될 수 있는 객체들은 무엇일까?
Iterable 객체, iterator
파이썬의 모든 것은 객체이다, 객체를 추상화하는 것이 클래스이고, 클래스들의 집합을 컨테이너라고 할 수 있다.
(컨테이너는 타입에 무관하게 여러 데이터 객체의 메모리 참조 정보를 저장하는 객체라고 할 수 있다)
그래서 리스트나 튜플, 딕셔너리, 세트들은 여러 데이터 객체들을 저장할 수 있기 때문에 컨테이너라고 할 수 있다.
다만, 이제 각 컨테이너들의 차이는 내부 데이터 구조의 구현에서 차이를 가진다. → 이런 특성으로 각각의 컨테이너 객체들은 상황에 따라 시간, 공간 복잡도에서 차이를 보인다.
모든 컨테이너들은 이터러블하다.
iteration은 enumeration하고 동일한 것은 아니다.
Itertaion하는 것을 비유하자면 마치, 사과를 사러갔는데, 판매자가 몇 개 남았는지 안 알려주는 것이다.

그래서 우리(개발자)는 사과가 몇 개 남았는지 알 수 없다. 그래서 사과를 원한다고 할 때 마다, 판매자(파이썬 인터프리터)가 사과가 남았으면 주고 안 남았으면 다 팔렸다고(StopIteration)할 것이다.
왜 이렇게 되는 것이나면, 이터레이터는 next 메서드를 제공하는 방식으로 해당 컨테이너 객체 안의 객체들을 순회하기 때문이다. next 메서드를 호출하게 되면 해당 컨테이너의 다음 객체를 가져오거나, 아니면 StopIteration 오류가 발생하게 된다.
예를 들어서 dictionary를 순회한다고 하면, next 함수가 중복이나 누락을 하지 않고 모든 요소를 하나씩 가져올 . 수 있도록 하는 것이다.
iteration의 동작 방식을 알아보자.
이터러블한 객체는 iter() 함수를 통해 이터레이터 객체를 반환한다. 그래서 next()함수를 통해서 순회가 가능하게 된다. for in 구문이 그것을 래핑한 것으로 이해하면 된다.
iter("1234") # <str_ascii_iterator object at 0x1029256f0> iter([1, 2, 3, 4]) # <list_iterator object at 0x1047c3250>정리하자면 이터레이터는 순서대로 다음 값을 반환할 수 있는 객체로 정의할 수 있다.
Generator
제너레이터는 조금 쉽게 이해하자면, 이터레이터의 게으른 버전으로 이해하면 된다.
이터레이터에서 요소들을 열거하기 위해서는 요소들을 미리 생성해야 한다.
예를 들어 1억 개의 요소로 구성된 리스트를 생성했다고 가정해보자. 각 요소가 생성된 후에 메모리에 저장될텐데, 그럴 경우에 매우 큰 양의 메모리를 차지하게 될 것이고, 더 커지게 되면, Out of Memory 오류가 발생할 가능성이 높아진다.

sum 메서드를 호출해서 리스트 요소의 총합을 구한다고 가정할 때, 리스트 자체를 메모리에 저장하는 것은 비효율적이다. 해당 리스트의 요소를 더할 때만 해당 값을 불러와서 더하는 방식로 해당 시점의 값만 알고 버리면 된다. 해당 요소에 대한 메모리 저장만 하면 되기 때문에 효율적이다.
import sys # 리스트 컴프리헨션을 사용하여 리스트 생성 list_1 = [i for i in range(100000000)] # 제너레이터 표현식을 사용하여 제너레이터 생성 list_2 = (i for i in range(100000000)) # 각 객체의 메모리 사용량 측정 memory_usage_list_1 = sys.getsizeof(list_1) memory_usage_list_2 = sys.getsizeof(list_2) print("List memory usage:", memory_usage_list_1, "bytes") print("Generator memory usage:", memory_usage_list_2, "bytes") >>> List memory usage: 835128600 bytes Generator memory usage: 200 bytes그러한 맥락으로 등장한 것이 제너레이터다.
제너레이터는 iterator처럼 많은 양의 메모리를 차지하지 않는다. 그리고 사용할 때만 호출된다는 것을 확인할 수 있다.
제너레이터를 초기화할 때 실행되지 않고 제너레이터 객체를 반환한다.
제너레이터 객체는 yield 문을 활용해서 실행을 중단하고 값을 반환할 수 있으며 다음 호출에서 중단된 지점부터 계속 실행되게 된다.
이를 통해 메모리에 모든 결과를 저장하지 않고도 값을 순차적으로 반환할 수 있다.
def generator(k): i = 1 while True: yield i**k i += 1 gen_1 = generator(1) gen_3 = generator(3) print(gen_1) print(gen_3) def get_sum(n): for i in range(n): next_1 = next(gen_1) next_3 = next(gen_3) print("next_1 = {}, next_3 = {}".format(next_1, next_3)) get_sum(8) >>> <generator object generator at 0x100f0f510> <generator object generator at 0x100fe8040> next_1 = 1, next_3 = 1 next_1 = 2, next_3 = 8 next_1 = 3, next_3 = 27 next_1 = 4, next_3 = 64 next_1 = 5, next_3 = 125 next_1 = 6, next_3 = 216 next_1 = 7, next_3 = 343 next_1 = 8, next_3 = 512해당 코드를 통해 제너레이터의 동작 방식을 알 수 있다. 핵심은 next, yield 메서드다.
처음에 genertor함수를 통해 제너레이터 객체를 생성하게 된다.
이후 해당 함수가 실행되는 것은 next함수의 인자로 해당 제너레이터 객체를 넣었을 때 실행된다.
그리고 yield 함수가 next 함수에 대한 반환 역할을 수행한다고 이해하면 된다.
그래서 매번 next 함수가 호출될 때마다. 중단된 지점인 yield아래부터 실행이 되게 된다.
그리고 동시에 해당 함수의 지역 변수는 값을 계속 갖고 있다.
코드를 보면 next_1과 next_3의 값이 계속 증가하는 것을 확인할 수 있다.
또한 제너레이터는 무한 집합이 될 수 있다. next()를 호출하기만 하면 제너레이터가 연산에 따라 자동으로 새 요소를 생성한 다음 사용자에게 반환하기 때문이다.
요약
- 컨테이너는 이터러블 객체다. 이터러블 객체는 iter() 함수를 호출하여 이터레이터를 가져올 수 있다.
- 이터레이터는 next() 함수를 사용하여 다음 요소를 가져올 수 있으므로 이를 통해 순회할 수 있다.
- 제너레이터는 특별한 유형의 이터레이터다(역방향 로직은 수행되지 않는다).
- 제너레이터를 사용하면 보다 명확한 코드를 작성할 수 있으며, 제너레이터를 합리적으로 사용하면 메모리 사용량을 줄이고 프로그램 구조를 최적화하며 프로그램 속도를 향상시킬 수 있다.
추가적으로 알아볼 사항
- 요소의 수가 유한한 제너레이터의 경우 반복이 완료된 이후에도 next()를 호출하면 어떻게 될까?
> 제너레이터가 소진된 후(즉, 모든 요소가 반복된 후) next() 함수를 사용하여 다음 항목을 검색하려고 하면 StopIteration 예외가 발생한다. 이 예외는 이터레이터에 더 이상 제공할 항목이 없음을 알리는 예외이다. - 제너레이터를 여러번 순회할 수 있을까?
> 제너레이터(generator)는 일반적으로 한 번만 순회할 수 있다. 제너레이터는 이터레이터의 한 형태로, 데이터를 메모리에 저장하지 않고 필요할 때마다 하나씩 생성(yield)한다.
> 대안으로 제너레이터를 다시 생성하거나, 이터러블 객체를 활용하는 방식이 있다.
728x90'개발 공부의 시작 > python' 카테고리의 다른 글
파이썬 비동기 프로그래밍 - (1) 코루틴 (0) 2024.01.18 [Python] FAST API에서 async def, def의 차이 (0) 2024.01.18 [Python] Del의 시간복잡도 (1) 2023.12.03