프로그래밍/Python

고급 자료구조: 파이썬 제너레이터 완벽 가이드

shimdh 2025. 2. 26. 09:20
728x90

제너레이터(Generator) 는 파이썬에서 메모리 효율적으로 데이터를 생성하고 처리할 수 있는 강력한 기능입니다. 일반적인 함수와 유사하지만, 한 번에 하나의 값만 반환하면서 상태를 유지하는 특징이 있어 대용량 데이터 처리, 무한 시퀀스 생성, 반복적인 연산 수행 등에 최적화되어 있습니다.

전통적인 리스트나 튜플을 사용하면 모든 데이터를 메모리에 한꺼번에 저장해야 하기 때문에 메모리 사용량이 증가하지만, 제너레이터는 필요할 때마다 값을 생성하여 반환하므로 메모리 부담을 줄일 수 있습니다.

이번 글에서는 제너레이터의 개념, 활용법, 성능 비교 및 실전 활용 사례까지 상세히 다뤄보겠습니다.


1. 제너레이터란?

1.1 제너레이터의 핵심 개념

제너레이터는 일반적인 함수와 비슷하지만, return 대신 yield 키워드를 사용하여 값을 하나씩 반환합니다. 함수가 yield를 만나면 값을 반환하고 실행을 일시 중지하며, 다음 호출이 발생하면 이전 상태를 유지한 채 이어서 실행됩니다.

1.2 제너레이터의 주요 특징

지연 실행(Lazy Evaluation)

  • 필요한 순간에만 값을 생성하여 반환하므로 메모리 절약 가능
  • 모든 데이터를 미리 메모리에 로드하지 않고 실시간으로 처리

상태 유지(State Retention)

  • 함수가 중단된 이후에도 이전 상태를 유지한 채 실행을 재개
  • 반복문 내부에서 데이터를 처리할 때 특히 유용

무한 시퀀스 처리 가능

  • 리스트는 한정된 크기를 가지지만, 제너레이터는 끝없이 값을 생성할 수 있음
  • 피보나치 수열, 무한 카운터, 데이터 스트림 처리 등에 활용

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)

# for 루프를 이용한 출력
for number in counter:
    print(number)

출력:

1
2
3
4
5

💡 yield를 사용하면 한 번에 하나의 값을 반환하면서 이전 상태를 유지한 채 계속 실행할 수 있습니다.


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

리스트 컴프리헨션과 유사하게 제너레이터 표현식을 사용하면 더 간결한 코드로 제너레이터를 만들 수 있습니다.

3.1 기본적인 제너레이터 표현식

squares = (x * x for x in range(10))  # 리스트 컴프리헨션과 유사하지만 () 사용

for square in squares:
    print(square)

출력:

0
1
4
9
16
25
36
49
64
81

💡 리스트 컴프리헨션 [x * x for x in range(10)]과 비교하면 메모리를 절약할 수 있는 장점이 있습니다.


4. 제너레이터의 실제 활용 사례

4.1 대량 데이터 처리

대용량 데이터를 한꺼번에 메모리에 올리면 메모리 부족 문제가 발생할 수 있습니다. 제너레이터를 활용하면 필요한 데이터만 즉시 생성하여 처리할 수 있습니다.

✅ 큰 파일을 한 줄씩 읽기

def read_large_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            yield line.strip()  # 한 줄씩 반환

# 사용 예
for line in read_large_file('big_data.txt'):
    print(line)  # 한 줄씩 출력

💡 일반적인 파일 읽기 방식 file.readlines()를 사용하면 모든 내용을 한 번에 메모리에 로드해야 하지만, 제너레이터를 사용하면 한 줄씩 읽어서 메모리 사용량을 줄일 수 있습니다.


4.2 무한 시퀀스 생성

리스트는 유한한 크기를 가지지만, 제너레이터는 무한한 값을 생성할 수 있어 반복 작업에 적합합니다.

✅ 피보나치 수열 생성

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b  # 다음 수 계산

# 피보나치 수열 생성
fib_gen = fibonacci()

# 첫 10개 값 출력
for _ in range(10):
    print(next(fib_gen))

출력:

0
1
1
2
3
5
8
13
21
34

💡 리스트를 사용하면 미리 크기를 정해야 하지만, 제너레이터를 활용하면 필요할 때마다 새로운 값을 생성할 수 있습니다.


5. 제너레이터와 일반 리스트의 성능 비교

제너레이터가 얼마나 메모리를 절약하는지 실제 코드로 확인해보겠습니다.

import sys

# 리스트 사용
list_nums = [x for x in range(10**6)]
print("리스트 메모리 크기:", sys.getsizeof(list_nums), "bytes")

# 제너레이터 사용
gen_nums = (x for x in range(10**6))
print("제너레이터 메모리 크기:", sys.getsizeof(gen_nums), "bytes")

출력 예시:

리스트 메모리 크기: 8697464 bytes
제너레이터 메모리 크기: 208 bytes

💡 리스트는 전체 데이터를 메모리에 저장하지만, 제너레이터는 다음 값을 생성할 때까지 메모리를 거의 사용하지 않습니다.


6. 결론: 제너레이터를 언제 사용할까?

대용량 데이터 처리
무한한 데이터 생성 (예: 피보나치 수열, 카운터)
파일 읽기 및 데이터 스트림 처리
메모리 최적화가 필요한 경우
빠른 실행이 필요한 반복 연산

제너레이터는 데이터를 실시간으로 처리하고, 메모리 사용을 최소화하며, 프로그램 성능을 최적화할 수 있는 강력한 도구입니다. 특히 대량의 데이터를 다루거나 지속적으로 값을 생성해야 하는 경우 제너레이터를 활용하면 보다 효율적인 코드를 작성할 수 있습니다.

728x90