파이썬 yield 제너레이터와 효율적인 반복 처리

파이썬 yield 제너레이터와 효율적인 반복 처리

Python
DevOps
파이썬 yield 제너레이터와 효율적인 반복 처리
Author

gabriel yang

Published

October 1, 2024


파이썬에는 yield라는 강력한 명령어가 있습니다. yield는 함수에서 값을 반환하는 역할을 하지만, 함수의 실행을 종료하지 않고 중단하여 제너레이터(generator)를 생성합니다. 이 글에서는 yield의 동작 방식, 이를 사용하는 이유, 그리고 제너레이터가 반복 처리를 효율적으로 수행하는 방법을 설명합니다.

1. yield란?

파이썬의 yield는 함수를 제너레이터로 변환하는 키워드입니다. 일반적으로 함수는 return 키워드를 사용해 값을 반환하고 실행을 종료합니다. 그러나 yield를 사용하면 함수가 값을 반환한 후에도 그 상태를 기억하고 다시 호출될 때 이전 상태부터 계속 실행됩니다.

yield의 동작 방식:

  • 함수가 호출되면 실행을 시작하고, yield를 만나면 값을 반환한 후 함수 실행을 일시 중지합니다.
  • 다음번에 해당 함수를 호출할 때, 이전에 중단된 부분부터 실행이 재개됩니다.
def simple_generator():
    yield 1
    yield 2
    yield 3

gen = simple_generator()

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

위의 예에서 simple_generator 함수는 yield를 사용하여 값을 반환합니다. 이 함수는 한 번에 하나의 값을 반환하며, 이후 호출될 때마다 중단된 지점부터 다시 실행됩니다.

2. yield와 제너레이터

yield제너레이터를 생성하는 핵심 요소입니다. 제너레이터는 데이터를 미리 계산해 메모리에 저장하는 대신, 필요할 때마다 계산해서 반환합니다. 이 방식은 메모리를 효율적으로 사용하게 하고, 특히 대용량 데이터를 처리할 때 유용합니다.

제너레이터의 장점:

  1. 메모리 절약: 제너레이터는 한 번에 하나의 값을 생성하므로, 대규모 데이터를 처리할 때도 메모리 사용량이 적습니다.
  2. 지연 실행(Lazy Evaluation): 제너레이터는 값을 필요할 때 계산하여 반환하므로, 불필요한 연산을 피할 수 있습니다.
  3. 무한 시퀀스 처리: 제너레이터는 끝이 없는 반복이나 무한 시퀀스를 처리할 때 유용합니다.

3. yieldreturn의 차이점

return은 값을 반환하고 함수의 실행을 종료합니다. 반면, yield는 값을 반환하지만 함수 실행을 중단하고 상태를 기억한 채로 대기합니다. 즉, 제너레이터 함수는 호출될 때마다 값을 하나씩 차례로 반환하는 반복자(iterator)의 역할을 합니다.

def simple_return():
    return 1
    return 2  # 이 코드는 실행되지 않음

def simple_yield():
    yield 1
    yield 2

print(simple_return())  # 출력: 1
gen = simple_yield()
print(next(gen))  # 출력: 1
print(next(gen))  # 출력: 2
1
1
2

return을 사용한 함수는 첫 번째 값을 반환하고 즉시 종료되지만, yield를 사용한 함수는 중단된 지점에서 다시 실행됩니다.

4. 제너레이터를 사용하는 이유

1) 큰 데이터 세트 처리

제너레이터는 메모리를 효율적으로 사용하기 때문에, 대용량 데이터나 무한 시퀀스를 처리할 때 매우 유용합니다. 예를 들어, 10억 개의 숫자를 생성해야 한다면 리스트에 모든 값을 저장하는 것은 메모리 측면에서 비효율적입니다. 대신 제너레이터를 사용하면 하나씩 값을 생성하고 처리할 수 있습니다.

def large_range():
    n = 0
    while n < 10**9:
        yield n
        n += 1

이 예시에서 large_range 함수는 무한에 가까운 숫자를 생성하지만, 메모리에는 하나의 값만 저장됩니다.

2) 지연 평가(Lazy Evaluation)

제너레이터는 필요할 때마다 값을 계산해 반환하기 때문에 지연 평가(Lazy Evaluation)를 수행합니다. 즉, 다음 값이 요청될 때까지 계산이 미뤄져 불필요한 작업을 방지할 수 있습니다.

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

위의 코드는 무한 루프를 실행하는 제너레이터로, 요청할 때마다 하나씩 값을 반환합니다. 제너레이터는 값을 미리 계산해두지 않으므로, 필요할 때마다 값을 생성합니다.

3) 파이프라인 처리

제너레이터는 다른 제너레이터와 결합하여 파이프라인 처리에도 사용할 수 있습니다. 데이터의 흐름을 제어하면서 각 단계에서 필요한 연산을 적용할 수 있습니다.

def generator1():
    for i in range(5):
        yield i

def generator2(gen):
    for value in gen:
        yield value * 2

gen = generator2(generator1())
print(list(gen))  # 출력: [0, 2, 4, 6, 8]
[0, 2, 4, 6, 8]

여기서 generator1은 0부터 4까지의 숫자를 생성하고, generator2는 이 값을 받아서 2배로 변환한 후 반환합니다.

5. yield 사용 시 주의할 점

  1. 한 번만 반복 가능: 제너레이터는 한 번 실행되면 끝까지 진행됩니다. 다시 반복하려면 제너레이터를 새로 생성해야 합니다.

    gen = simple_generator()
    list(gen)  # 출력: [1, 2, 3]
    list(gen)  # 출력: []
  2. 제너레이터 상태를 추적: 제너레이터는 상태를 유지하며 실행되기 때문에, 함수 내에서 값을 추적하고 관리해야 합니다. 이는 복잡한 로직을 구성할 때 유의해야 할 점입니다.

  3. 예외 처리: 제너레이터 안에서 예외가 발생하면 제너레이터가 종료됩니다. 따라서 제너레이터 내부에서도 적절한 예외 처리가 필요합니다.

6. 실제 사용 사례

1) 파일 읽기

파일을 한 줄씩 처리할 때 제너레이터를 사용하면 메모리 사용을 줄일 수 있습니다.

def read_file_line_by_line(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            yield line.strip()

# 파일에서 한 줄씩 읽기
for line in read_file_line_by_line('example.txt'):
    print(line)

이 코드는 파일을 한 번에 전체 읽지 않고, 한 줄씩 처리합니다. 이는 매우 큰 파일을 처리할 때 유용합니다.

2) 네트워크 요청

네트워크 요청을 비동기적으로 처리하면서 결과를 순차적으로 반환할 때도 제너레이터가 유용합니다.

7. 결론

파이썬의 yield는 함수의 실행을 중단하고, 필요한 시점에서 값을 반환하는 강력한 도구입니다. 이를 통해 제너레이터를 만들면, 메모리를 절약하면서 대규모 데이터를 처리하거나, 무한 시퀀스를 효율적으로 다룰 수 있습니다. 특히 반복 작업이나 지연 평가가 필요한 상황에서 제너레이터와 yield를 적절히 활용하면 성능과 코드 가독성을 모두 개선할 수 있습니다.