티스토리 뷰

목차




    Python Logo

    서론

    프로그래밍을 하다 보면 데이터가 많아질수록 코드가 복잡해지고, 메모리 사용량을 최적화하는 일이 중요해집니다. 반복자(iterator)와 제너레이터(generator)는 Python에서 이런 문제를 해결하는 강력한 도구인데요. 처음엔 조금 생소할 수 있지만, 실제 프로젝트에서도 유용하게 쓰일 수 있는 기술이니 꼭 익혀 두세요. 저도 처음엔 어려웠지만, 이 두 개념을 이해한 후로 코드를 훨씬 효율적으로 작성할 수 있었던 경험을 많이 했습니다. 그러니 이번 기회에 제대로 배워봅시다.


    1. 반복자(Iterator)란 무엇인가?

    Python에서 **반복자(iterator)**는 데이터를 한 번에 하나씩 순차적으로 접근할 수 있도록 해주는 객체입니다. 대부분의 Python 컬렉션 타입, 예를 들어 리스트(list), 튜플(tuple), 딕셔너리(dictionary), 문자열(string) 등은 반복 가능한(iterable) 객체입니다.

    1.1 기본 반복자 구조

    반복자를 이해하려면 **iter()**와 next() 함수를 알아야 합니다. iter() 함수는 반복 가능한 객체를 반복자로 변환해 주고, next() 함수는 반복자에서 다음 항목을 가져오는 역할을 합니다.

    예시:

    # 리스트를 반복자로 변환
    numbers = [1, 2, 3, 4, 5]
    iterator = iter(numbers)
    
    # next() 함수를 사용해 하나씩 값 추출
    print(next(iterator))  # 출력: 1
    print(next(iterator))  # 출력: 2

    1.2 반복자의 작동 원리

    반복자는 데이터를 순차적으로 반환하다가 데이터가 끝나면 StopIteration 예외를 발생시켜 반복을 종료합니다. 반복자는 일반적인 for 루프와도 밀접한 관련이 있으며, for 문이 내부적으로 next()iter()를 사용해 작동합니다.

    예시:

    numbers = [1, 2, 3]
    iterator = iter(numbers)
    
    while True:
        try:
            item = next(iterator)
            print(item)
        except StopIteration:
            break  # 반복 종료

    2. 제너레이터(Generator)란 무엇인가?

    제너레이터는 반복자와 유사하지만, 값들을 한 번에 모두 메모리에 저장하지 않고 필요할 때마다 계산하는 방식으로 데이터를 반환하는 함수입니다. 제너레이터는 yield 키워드를 사용해 각 값을 반환하며, 메모리를 효율적으로 관리할 수 있다는 장점이 있습니다.

    2.1 제너레이터 함수 만들기

    yield 키워드를 사용해 제너레이터 함수를 정의합니다. yield는 함수의 실행을 잠시 멈추고 값을 반환한 후, 함수가 다시 호출되면 이전 상태에서 재개됩니다.

    예시:

    def count_up_to(max):
        count = 1
        while count <= max:
            yield count
            count += 1
    
    # 제너레이터 함수 호출
    counter = count_up_to(5)
    
    # 값을 하나씩 추출
    for num in counter:
        print(num)  # 1, 2, 3, 4, 5

    2.2 제너레이터의 장점과 활용

    제너레이터는 메모리 효율성이 뛰어납니다. 특히, 대용량 데이터나 무한대의 값을 생성해야 하는 경우 제너레이터를 사용하면 필요할 때마다 값을 생성하여 메모리 낭비를 최소화할 수 있습니다.

    예시: 무한대의 값을 생성하는 제너레이터

    def infinite_sequence():
        num = 0
        while True:
            yield num
            num += 1

    이 예제의 제너레이터는 무한히 숫자를 생성하지만, 필요한 만큼만 호출하여 사용할 수 있습니다.


    3. 반복자와 제너레이터의 차이점

    반복자와 제너레이터는 비슷한 기능을 하지만, 몇 가지 차이점이 있습니다.

    3.1 반복자와 제너레이터 비교표

     
    항목반복자제너레이터
    사용 방식 iter()next()로 사용 yield를 사용하여 함수 작성
    데이터 저장 모든 데이터를 메모리에 저장 필요할 때마다 계산
    상태 유지 상태 유지 불가 yield를 통해 상태 유지
    메모리 사용 메모리를 많이 소모 메모리 효율적

    3.2 반복자와 제너레이터의 예제 비교

    예를 들어, 1부터 10까지의 값을 생성하는 반복자와 제너레이터를 비교해보겠습니다.

    • 반복자 방식:
    class CountIterator:
        def __init__(self, max):
            self.max = max
            self.current = 0
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.current < self.max:
                self.current += 1
                return self.current
            raise StopIteration
    
    # 사용 예
    counter = CountIterator(5)
    for num in counter:
        print(num)
    • 제너레이터 방식:
    def count_up_to(max):
        count = 1
        while count <= max:
            yield count
            count += 1
    
    # 사용 예
    for num in count_up_to(5):
        print(num)

    4. 제너레이터 표현식 (Generator Expression)

    제너레이터 표현식은 제너레이터를 한 줄로 작성할 수 있게 해주는 간결한 방법입니다. 리스트 컴프리헨션과 비슷하지만, 대괄호 대신 소괄호를 사용하여 메모리를 절약할 수 있는 장점이 있습니다.

    4.1 기본 제너레이터 표현식 구조

    # 예: 제너레이터 표현식
    squares = (x * x for x in range(10))
    
    for square in squares:
        print(square)  # 0, 1, 4, 9, ..., 81

    제너레이터 표현식은 메모리 효율성이 높으며 코드가 간결해집니다.

    4.2 제너레이터 표현식과 리스트 컴프리헨션 비교

    제너레이터 표현식은 메모리에서 값을 저장하지 않고, 호출할 때마다 값을 계산하여 반환합니다. 반면, 리스트 컴프리헨션은 모든 결과를 메모리에 저장하므로 큰 데이터에서는 제너레이터가 더 효율적입니다.

    # 리스트 컴프리헨션
    squares_list = [x * x for x in range(10)]
    
    # 제너레이터 표현식
    squares_gen = (x * x for x in range(10))
    
    print(sum(squares_list))  # 리스트 전체 합 계산
    print(sum(squares_gen))   # 제너레이터로 합 계산 (메모리 절약)

    5. 실습 예제 – 제너레이터와 반복자 활용하기

    반복자와 제너레이터를 활용한 예제를 통해 실전에서 자주 쓰이는 용도를 익혀봅시다.

    5.1 대용량 파일 읽기

    제너레이터는 대용량 파일을 메모리 부족 걱정 없이 한 줄씩 읽는 데 유용합니다. 다음은 yield를 사용해 파일의 각 줄을 순차적으로 처리하는 예제입니다.

    def read_large_file(file_path):
        with open(file_path, 'r') as file:
            for line in file:
                yield line
    
    # 파일 읽기 예제
    for line in read_large_file("large_data.txt"):
        print(line.strip())

    5.2 페이징 기능 구현

    제너레이터를 사용해 대량의 데이터를 페이지 단위로 나눠 처리하는 방식으로, 데이터베이스의 페이징 기능을 흉내 낼 수 있습니다.

    def paginate(items, page_size):
        for i in range(0, len(items), page_size):
            yield items[i:i + page_size]
    
    # 예제 데이터
    data = list(range(1, 21))  # 1부터 20까지의 데이터
    
    # 페이징 처리 예제
    for page in paginate(data, 5):
        print("Page:", page)

    5.3 무한 수열 생성

    제너레이터는 필요할 때마다 호출하는 방식이기 때문에, 무한 수열 생성에도 적합합니다. 예를 들어, 피보나치 수열을 제너레이터로 생성할 수 있습니다.

    def fibonacci():
        a, b = 0, 1
        while True:
            yield a
            a, b = b, a + b
    
    # 무한 피보나치
    
     수열 생성 예제
    fib = fibonacci()
    for _ in range(10):
        print(next(fib))

    요약 및 결론

    Python의 반복자와 제너레이터는 효율적인 데이터 처리를 가능하게 해주는 매우 강력한 도구입니다. 특히 대용량 데이터 처리에서 반복자와 제너레이터는 메모리 효율성을 극대화하며, 제너레이터 표현식을 통해 코드의 간결함을 유지할 수 있습니다. 반복자와 제너레이터의 특성을 잘 이해하고 활용한다면, Python 코드의 성능을 높이고 메모리 자원을 절약하는 데 큰 도움이 될 것입니다.


    디스크립션: Python의 반복자와 제너레이터는 대용량 데이터 처리와 메모리 관리에서 중요한 역할을 합니다. 반복자와 제너레이터의 개념과 활용 방법을 초보 개발자도 이해하기 쉽게 실습 예제와 함께 배워보세요.