[python] 비동기처리를 알아보자: iteraotr와 genrator

정재혁·2024년 10월 19일
0
post-thumbnail

iterator와 iterable

  • 이터러블(iterable)

    • 반복할 수 있는 객체입니다. __iter__() 메서드를 가지고 있어 이터레이터를 생성할 수 있는 객체를 말합니다. 리스트, 튜플, 문자열, 딕셔너리 등은 모두 이터러블입니다.
  • 이터레이터(iterator)

    • 이터레이터는 데이터 스트림을 나타내는 객체입니다.

    • 이터레이터의 __next__() 메서드를 반복해서 호출하거나 이터레이터를 next() 내장 함수에 전달하면 데이터 스트림의 연속적인 항목을 반환합니다.

    • 더 이상 반환할 데이터가 없을 경우, StopIteration 예외가 발생합니다. 이후에 __next__() 메서드를 다시 호출하면 계속해서 StopIteration 예외만 발생합니다.

    • 이터레이터는 iter() 메서드를 반드시 가지고 있어야 하며, 이 메서드는 이터레이터 객체 자체를 반환합니다. 따라서 모든 이터레이터는 반복 가능한 객체(iterable)이기도 하며, 다른 반복 가능한 객체가 허용되는 대부분의 곳에서 사용될 수 있습니다.


# 직접 만든 iterator class Counter
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
        else:
            self.current += 1
            return self.current - 1

counter = Counter(1, 5)
for num in counter:
    print(num)

Generator

  • generator 함수가 중단된 상태를 유지하며 다시 실행할 수 있게 해주는 파이썬의 강력한 기능입니다.
  • 일반적으로, 상태를 유지하며 값을 생성하는 함수는 콜백 함수나 전역 변수와 같은 복잡한 방식으로 구현해야 하지만, 제너레이터를 사용하면 간단하게 구현할 수 있습니다
  • yield 키워드를 사용해 값을 반환하고, 다음 호출 시 함수는 이전 상태를 유지한 채 이어서 실행됩니다.
# generator 함수의 예시

def generate_ints(N):
    for i in range(N):
        yield i

>>> gen = generate_ints(3)
>>> gen
<generator object at 0x8117f90>
>>> gen.next()
0
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 2, in generate_ints
StopIteration

위의 예시만으로는 도대체 이게 왜 좋은건지 이해가 되지 않을 수 있습니다.
PEP 255 – Simple Generators 의 Motivation을 보면서 generator 가 생긴 이유를 알아봅시다.

생성자 함수가 상태를 유지하면서 값을 생성해야 하는 경우,
대부분의 프로그래밍 언어에서는 생성자 함수의 인수 목록에 콜백 함수를 추가하여 각 값을 생성할 때마다 이를 호출하는 방법 외에는 효율적이고 간단한 해결책을 제공하지 않습니다.
예를 들어, 파이썬 표준 라이브러리의 tokenize.py는 이러한 접근 방식을 사용합니다. 호출자는 tokenize() 함수에 tokeneater 함수를 전달해야 하며, tokenize()가 다음 토큰을 찾을 때마다 이 함수가 호출됩니다.

tokenizer가 무엇인지 몰라도 괜찮습니다.
중요한 것은

  • tokenizer가 다음 토큰을 return 해야 한다는 것
  • tokenizer의 생성자에 tokeneater라는 함수를 전달하여, 다음 토큰을 return 할 때 호출한다는 것
    입니다.

계속 이어서 봅시다

또 다른 대안은 tokenize를 이터레이터로 만들어, .next() 메서드가 호출될 때마다 다음 토큰을 반환하도록 하는 것입니다. 이렇게 하면 대규모 리스트의 결과를 제공하는 것과 마찬가지로 사용하기 편리하지만, 메모리와 "중간에 그만두고 싶을 때는 어떻게 할 것인가?"라는 문제점을 해결할 수 있습니다. 그러나 이 경우, tokenize가 .next() 호출 사이에 상태를 기억해야 하며, tokenize.tokenize_loop()를 보면 이것이 얼마나 어렵고 복잡한 작업인지 알 수 있습니다. 또는 일반적인 트리 구조의 노드를 생성하는 재귀적 알고리즘을 이터레이터 프레임워크에 맞추려면 재귀를 수동으로 제거하고 트래버설 상태를 직접 관리해야 하는 어려움이 생깁니다.

위에서 언급한 iterator가 나오는 군요!
함수를 받아서 사용하는 것이 아니라, 내부적으로 iterator를 구현하는 방법이 있다고 설명하고 있습니다. 그러나, 이 역시 구현에 복잡함이 있다고 하네요.

PEP 255 – Simple Generators 의 Motivation 에서는 이 외에도 몇가지 방법을 소개하지만, 모든 방법의 기본 개념은 동일합니다.

호출자에게 중간 결과를 반환할 수 있는 함수를 제공하되, 함수의 로컬 상태를 유지하여 함수가 중단된 지점에서 다시 실행될 수 있도록 하는 것

프로그래밍 언어 수준에서 이를 지원하지 않으면, 실제로 구현하기는 매우 복잡하고 어렵다는 뜻입니다.
그래서 Generator와 yield가 나오게 된 것입니다.

출처

profile
궁금한 것을 궁금한 것으로 두지 말자.
post-custom-banner

0개의 댓글