본문 바로가기

개념

[Python] 제네레이터 (generator) 예시

Python 제네레이터 (generator)는 반복자(iterator)를 만드는 함수입니다. 일반적으로 반복자는 메모리에 모든 값을 로드하여 사용합니다. 그러나 제네레이터는 값을 필요할 때마다 생성하므로 대규모 데이터 집합을 다룰 때 효율적입니다.

 

1. 개념

 

제네레이터 함수는 일반 함수와 동일한 방법으로 정의되지만, 일반적으로 yield 키워드를 사용하여 값을 반환합니다. 제네레이터 함수가 호출되면, 함수 내의 코드가 실행되는 대신 제네레이터 객체가 반환됩니다. 이 객체는 제네레이터 함수 내의 코드를 실행하는 데 사용됩니다.

제네레이터 함수는 yield를 만날 때마다 값을 생성합니다. 제네레이터는 값을 생성한 후 일시 중지됩니다. 함수가 다시 호출되면 일시 중지된 지점에서 다시 시작하고, yield를 만나면 다음 값을 생성합니다. 이러한 방식으로, 제네레이터는 필요한 만큼의 값을 생성하고, 필요하지 않은 값을 생성하지 않습니다.

예를 들어, 아래 코드는 1에서 10까지의 제곱 값을 생성하는 제네레이터 함수입니다.

 

def squares(n):
    for i in range(1, n+1):
        yield i*i


제네레이터 함수를 호출하면, 아래와 같이 제네레이터 객체가 반환됩니다.

 

sq = squares(10)


이후, 제네레이터 객체는 반복문에서 사용될 수 있습니다. 이때 제네레이터 함수는 필요한 만큼의 제곱 값을 생성합니다.

 

for i in sq:
    print(i)


위 예제는 1에서 10까지의 제곱 값을 생성하는 제네레이터 함수를 정의하고, 제네레이터 객체를 생성한 후 반복문을 사용하여 값들을 출력하는 방법을 보여줍니다.

 

2. 예제 코드


1) 무한 수열 생성


제네레이터 함수를 사용하면 무한 수열도 생성할 수 있습니다. 예를 들어, 아래 코드는 무한한 소수를 생성하는 제네레이터 함수입니다.

 

def primes():
    yield 2
    prime_list = [2]
    n = 3
    while True:
        for p in prime_list:
            if n % p == 0:
                break
        else:
            prime_list.append(n)
            yield n
        n += 2


이 함수는 2부터 시작하여 모든 소수를 생성합니다. 제네레이터를 사용하면 무한한 소수를 생성할 수 있으며, 필요한 만큼만 가져올 수 있습니다.

 

p = primes()
for i in range(10):
    print(next(p))


위 예제는 10개의 소수를 생성하는 방법을 보여줍니다.


2) 파일 처리


제네레이터 함수를 사용하면 대용량 파일 처리도 가능합니다. 예를 들어, 아래 코드는 텍스트 파일에서 한 줄씩 읽어서 처리하는 제네레이터 함수입니다.

 

def read_file(filename):
    with open(filename, 'r') as f:
        for line in f:
            yield line.strip()

이 함수는 파일에서 한 줄씩 읽어서 반환합니다. 파일이 매우 큰 경우, 메모리 부족 문제를 피할 수 있습니다.

 

for line in read_file('data.txt'):
    print(line)


위 예제는 data.txt 파일에서 한 줄씩 읽어서 출력하는 방법을 보여줍니다.


3) 파이프라인 처리


제네레이터 함수는 파이프라인 처리에도 사용할 수 있습니다. 예를 들어, 아래 코드는 파일에서 한 줄씩 읽은 후, 각 줄을 대문자로 변환하는 제네레이터 함수입니다.

 

def read_file(filename):
    with open(filename, 'r') as f:
        for line in f:
            yield line.strip()

def to_upper(iterable):
    for item in iterable:
        yield item.upper()


이 함수들을 조합하여 파일에서 읽은 내용을 대문자로 출력할 수 있습니다.

 

for line in to_upper(read_file('data.txt')):
    print(line)


위 예제는 data.txt 파일에서 한 줄씩 읽어서 대문자로 출력하는 방법을 보여줍니다.


3. 장점

 

  • 효율적인 메모리 관리: 제네레이터는 한 번에 모든 값을 생성하지 않고 필요할 때마다 값을 생성하므로, 메모리를 효율적으로 사용할 수 있습니다. 이는 대용량 데이터 처리와 같은 상황에서 매우 유용합니다.
  • 지연 평가(lazy evaluation): 제네레이터는 값을 생성할 때마다 yield 키워드를 사용하여 값을 반환하므로, 값을 모두 계산할 필요가 없습니다. 필요한 값만 계산하고 반환할 수 있습니다. 이는 계산 비용이 큰 경우 유용합니다.
  • 파이프라인 처리: 제네레이터를 연결하여 파이프라인을 구성할 수 있으므로, 데이터 처리를 보다 간단하게 할 수 있습니다.

4. 단점

  • 한 번에 하나의 값을 반환한다는 점: 제네레이터는 한 번에 하나의 값을 반환하므로, 복잡한 연산을 수행하는 경우 코드가 지나치게 복잡해질 수 있습니다.
  • 상태를 저장하고 복원해야 한다는 점: 제네레이터 함수는 일시 중지된 상태에서 다시 시작할 수 있도록 상태를 저장하고 복원해야 합니다. 이는 구현이 복잡할 수 있습니다.

 

요약하자면, 제네레이터는 메모리 사용을 최적화하고 지연 평가를 지원하여 대용량 데이터 처리에 효과적입니다. 하지만 코드가 복잡해질 수 있으며, 구현이 복잡할 수 있습니다.