PYTHON

파이썬에서의 제너레이터(Generator) 이해하기

DevMaster!! 2024. 8. 15. 18:43

제너레이터(Generator)는 파이썬에서 반복 가능한(iterable) 객체를 생성하는 특별한 함수입니다. 제너레이터는 데이터를 한 번에 하나씩 처리하며, 필요한 시점에 값을 생성하여 메모리 사용을 최소화하는 장점이 있습니다. 이번 포스팅에서는 제너레이터의 개념과 사용법, 그리고 제너레이터를 활용한 다양한 예제들을 살펴보겠습니다.

1. 제너레이터란?

제너레이터는 이터레이터(Iterator)를 생성하는 함수로, yield 키워드를 사용하여 값을 반환합니다. 일반적인 함수는 값을 반환하고 종료되지만, 제너레이터는 yield 문을 만나면 현재 상태를 유지하고, 다음 호출 시에 이어서 실행됩니다. 이는 메모리를 효율적으로 사용하고, 무한대의 시퀀스나 큰 데이터셋을 처리할 때 매우 유용합니다.

1.1. 제너레이터의 기본 구조

def my_generator():
    yield 1
    yield 2
    yield 3

gen = my_generator()

print(next(gen))  # 출력: 1
print(next(gen))  # 출력: 2
print(next(gen))  # 출력: 3

위 코드에서는 my_generator 함수가 제너레이터로 정의되었습니다. next() 함수를 호출할 때마다 yield 문을 만나 제너레이터가 값을 반환하고, 다음 호출 시에는 이전 상태에서 이어서 실행됩니다.

2. 제너레이터의 동작 방식

2.1. yield 키워드

yield 키워드는 제너레이터 함수에서 값을 반환하고, 함수의 상태를 유지합니다. yield를 사용하여 값을 반환한 후, 함수는 중단 상태에 놓이며, 다음 호출에서 중단된 부분부터 실행이 재개됩니다.

def count_up_to(max):
    count = 1
    while count <= max:
        yield count
        count += 1

counter = count_up_to(5)
print(list(counter))  # 출력: [1, 2, 3, 4, 5]

2.2. 제너레이터의 이터레이션

제너레이터는 이터레이터이므로, for 루프를 사용하여 제너레이터 객체를 순회할 수 있습니다.

def count_down_from(max):
    while max > 0:
        yield max
        max -= 1

for number in count_down_from(5):
    print(number)

위 코드에서는 for 루프를 사용해 제너레이터를 순회하며, yield를 통해 반환된 값을 출력합니다.

3. 제너레이터 표현식

리스트 컴프리헨션과 유사하게, 제너레이터 표현식을 사용하면 간단한 구문으로 제너레이터를 생성할 수 있습니다. 제너레이터 표현식은 메모리 효율이 높은 코드 작성을 가능하게 합니다.

3.1. 제너레이터 표현식 기본 사용법

# 리스트 컴프리헨션과 비교
list_comp = [x * 2 for x in range(5)]
print(list_comp)  # 출력: [0, 2, 4, 6, 8]

# 제너레이터 표현식
gen_exp = (x * 2 for x in range(5))
print(list(gen_exp))  # 출력: [0, 2, 4, 6, 8]

제너레이터 표현식은 리스트 컴프리헨션과 비슷하지만, [] 대신 ()를 사용합니다. 제너레이터는 값을 한 번에 하나씩 생성하므로 메모리 사용이 적습니다.

4. 제너레이터의 활용 예제

제너레이터는 다양한 상황에서 유용하게 사용할 수 있습니다. 다음은 제너레이터를 활용한 몇 가지 실용적인 예제입니다.

4.1. 큰 파일의 라인 읽기

큰 파일을 처리할 때 모든 데이터를 한 번에 메모리에 로드하는 대신, 제너레이터를 사용하여 라인 단위로 파일을 처리할 수 있습니다.

def read_large_file(file_name):
    with open(file_name, 'r') as file:
        for line in file:
            yield line

for line in read_large_file('large_file.txt'):
    print(line.strip())

위 코드에서는 read_large_file 제너레이터를 사용해 파일의 라인을 하나씩 읽어들입니다. 이렇게 하면 메모리 사용을 최소화하면서 큰 파일을 처리할 수 있습니다.

4.2. 무한 시퀀스 생성

제너레이터는 무한 시퀀스를 생성하는 데에도 유용합니다. 예를 들어, 피보나치 수열을 무한히 생성할 수 있습니다.

def fibonacci_sequence():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib_gen = fibonacci_sequence()

for _ in range(10):
    print(next(fib_gen))

위 코드에서는 피보나치 수열을 생성하는 제너레이터를 정의했습니다. 이 제너레이터는 next() 함수 호출 때마다 피보나치 수를 하나씩 생성합니다.

4.3. 데이터 스트림 처리

제너레이터를 사용하면 실시간으로 들어오는 데이터 스트림을 처리할 때 메모리를 절약하면서 효율적으로 작업할 수 있습니다.

import random

def data_stream():
    while True:
        yield random.randint(1, 100)

stream = data_stream()

for _ in range(5):
    print(next(stream))

위 코드에서는 data_stream 제너레이터가 무작위 숫자를 생성하여 데이터 스트림을 모방합니다. 스트림 데이터를 필요할 때만 생성하여 처리할 수 있습니다.

5. 제너레이터의 장점과 단점

5.1. 장점

  • 메모리 효율: 제너레이터는 값을 한 번에 하나씩 생성하므로 메모리를 절약할 수 있습니다. 특히 큰 데이터셋을 처리할 때 유리합니다.
  • 지연 평가: 필요할 때만 값을 생성하므로, 계산 비용이 큰 작업을 지연시킬 수 있습니다.
  • 코드 간결성: 제너레이터를 사용하면 코드가 더 간결하고 이해하기 쉬워집니다.

5.2. 단점

  • 재사용 불가: 제너레이터는 한 번 순회한 후에는 재사용할 수 없습니다. 다시 사용하려면 새로 생성해야 합니다.
  • 디버깅 어려움: 제너레이터 내부 상태를 추적하고 디버깅하는 것이 일반 함수에 비해 더 어렵습니다.
  • 순차 접근: 제너레이터는 순차적으로 값을 생성하므로, 인덱스를 통해 임의 접근이 불가능합니다.

6. 제너레이터와 함수의 비교

제너레이터는 일반 함수와 달리 값을 반환하는 대신, 현재 상태를 유지하며 중단될 수 있습니다. 제너레이터는 데이터를 순차적으로 처리하는 데 유리하지만, 모든 데이터를 한 번에 반환해야 하는 경우에는 일반 함수가 더 적합할 수 있습니다.

# 일반 함수
def get_list():
    return [1, 2, 3]

print(get_list())  # 출력: [1, 2, 3]

# 제너레이터
def get_generator():
    yield 1
    yield 2
    yield 3

gen = get_generator()
print(list(gen))  # 출력: [1, 2, 3]

위 코드에서 get_list() 함수는 전체 리스트를 한 번에 반환하고, get_generator() 제너레이터는 값을 하나씩 생성하여 반환합니다.

결론

이번 포스팅에서는 파이썬에서 제너레이터를 이해하고 활용하는 방법에 대해 알아보았습니다. 제너레이터는 메모리 효율성을 높이고, 코드의 간결성을 유지하면서도 복잡한 데이터를 처리하는 데 유용한 도구입니다. 제너레이터의 기본 동작 방식, 표현식, 그리고 다양한 활용 예제를 실습하면서 제너레이터의 장점을 느껴보세요. 더 나아가 제너레이터를 활용한 데이터 스트림 처리나 무한 시퀀스 생성 등을 통해 실무에서도 유용하게 적용할 수 있을 것입니다.


이 글을 통해 파이썬의 제너레이터를 이해하고, 실습을 통해 이를 사용하는 방법을 익힐 수 있을 것입니다. 제너레이터의 강력한 기능을 활용해 더 효율적이고 읽기 쉬운 파이썬 코드를 작성해 보세요!