본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 제너레이터의 혁신, yield와 yield from의 3가지 결정적 차이점과 최적화 방법

by Papa Martino V 2026. 4. 2.
728x90

yield와 yield from의 3가지 결정적 차이
yield와 yield from의 3가지 결정적 차이

 

 

파이썬에서 대규모 데이터를 처리하거나 비동기 프로그래밍을 설계할 때 Generator(제너레이터)는 메모리 효율성을 극대화하는 핵심 도구입니다. 하지만 많은 개발자가 yieldyield from의 기능적 차이를 단순히 '코드가 짧아지는 문법적 설탕(Syntactic Sugar)'으로만 오해하곤 합니다. 본 포스팅에서는 실무 환경에서 성능 최적화와 코드 유지보수성을 결정짓는 두 키워드의 내부 동작 원리를 심도 있게 분석하고, 시니어 개발자가 프로젝트에 즉시 적용할 수 있는 7가지 고급 활용 사례를 제시합니다.


1. yield와 yield from의 개념적 배경과 동작 원리

yield는 함수의 실행을 일시 중지하고 호출자에게 값을 반환하며, 함수의 상태를 보존합니다. 반면, 파이썬 3.3에서 도입된 yield from은 단순한 반복을 넘어 '서브 제너레이터(Sub-generator)'와의 양방향 통신 채널을 구축합니다.

핵심 메커니즘의 차이

기존의 yield 방식은 중첩된 이터러블을 처리할 때 명시적인 for 루프를 필요로 했습니다. 하지만 yield from은 이터러블 객체를 자동으로 순회할 뿐만 아니라, 호출자(Caller)와 서브 제너레이터 사이에서 send(), throw(), close() 메서드를 투명하게 전달(Delegation)합니다.


2. yield vs yield from 비교 분석 요약

두 방식의 기술적 특징과 제어 흐름의 차이를 표로 정리하였습니다.

비교 항목 yield (Standard) yield from (Delegation)
주요 목적 단일 값의 순차적 반환 하위 이터레이터로 제어권 위임
코드 복잡도 중첩 순회 시 중복 루프 발생 단일 라인으로 평탄화(Flatten) 가능
양방향 통신 직접적인 send/throw 전달 불가 Caller와 Sub-gen 간 자동 연결
반환값 처리 반복 종료 후 값 반환 불가 StopIteration의 value 획득 가능
성능 효율 파이썬 레벨의 루프 오버헤드 내부 C 구현을 통한 속도 향상
예외 처리 수동으로 catch 후 전달 필요 내부에서 자동으로 상위로 전파

3. 실무 적용을 위한 7가지 Sample Examples

실제 엔지니어링 환경에서 마주할 수 있는 복잡한 데이터 구조와 파이프라인 설계 예시입니다.

Example 1: 다차원 리스트 평탄화 (Recursive Flattening)

복잡하게 중첩된 JSON 데이터나 트리를 1차원 리스트로 변환할 때 유용합니다.


def flatten(items):
    for item in items:
        if isinstance(item, list):
            # yield from을 사용해 재귀적으로 하위 요소 위임
            yield from flatten(item)
        else:
            yield item

data = [1, [2, [3, 4], 5], 6]
print(list(flatten(data)))  # [1, 2, 3, 4, 5, 6]

Example 2: 대용량 로그 파일 병합 (File Merger)

여러 개의 대형 로그 파일을 메모리 점유 없이 하나로 스트리밍합니다.


def read_logs(file_paths):
    for path in file_paths:
        with open(path, 'r') as f:
            yield from (line.strip() for line in f if line.startswith('ERROR'))

# 사용 예시
# error_stream = read_logs(['server1.log', 'server2.log'])

Example 3: 양방향 코루틴 파이프라인 (Bidirectional Communication)

send() 메서드를 통해 하위 코루틴에 데이터를 직접 주입하는 구조입니다.


def accumulator():
    total = 0
    while True:
        value = yield
        if value is None: break
        total += value
    return total

def proxy_gen():
    # yield from은 서브 제너레이터가 return하는 값을 변수에 저장할 수 있게 함
    result = yield from accumulator()
    yield f"Final Sum: {result}"

gen = proxy_gen()
next(gen)
gen.send(10)
gen.send(20)
print(gen.send(None))  # Final Sum: 30

Example 4: 데이터베이스 청크 처리 (Batch Processing)

DB에서 대량의 레코드를 가져와 일정 단위(Chunk)로 끊어서 처리하는 로직을 추상화합니다.


def fetch_chunks(cursor, batch_size):
    while True:
        rows = cursor.fetchmany(batch_size)
        if not rows: break
        yield from rows

# 비즈니스 로직에서 한 줄로 데이터 소모 가능
# for row in fetch_chunks(cursor, 1000): process(row)

Example 5: 예외 전파 테스트 (Error Handling Delegation)

서브 제너레이터 내부에서 발생하는 예외를 상위 호출자가 관리하는 구조입니다.


def sub_worker():
    try:
        yield "Working..."
    except ValueError:
        yield "Caught error in sub_worker"

def main_delegator():
    yield from sub_worker()

d = main_delegator()
print(next(d))
print(d.throw(ValueError)) # 하위 워커로 예외가 직접 전달됨

Example 6: 체이닝된 시퀀스 생성 (Chain Generators)

itertools.chain과 유사한 동작을 직접 구현하여 여러 시퀀스를 결합합니다.


def sequence_combiner(*iterables):
    for it in iterables:
        yield from it

# 리스트, 튜플, 제너레이터를 모두 하나로 연결
combined = sequence_combiner([1, 2], (3, 4), range(5, 7))

Example 7: 트리 구조 탐색 (DFS Implementation)

디렉토리 구조나 조직도 같은 계층 데이터를 깊이 우선 탐색(DFS)할 때 코드를 극도로 단순화합니다.


class Node:
    def __init__(self, val, children=[]):
        self.val = val
        self.children = children

    def walk(self):
        yield self.val
        for child in self.children:
            yield from child.walk()

root = Node('A', [Node('B', [Node('D')]), Node('C')])
print(list(root.walk())) # ['A', 'B', 'D', 'C']

4. 결론: 왜 yield from을 써야 하는가?

단순히 루프를 줄이는 것을 넘어, yield from제어의 역전(Inversion of Control)을 제너레이터 수준에서 구현합니다. 특히 서브 제너레이터가 반환하는 최종 결과값(return statement)을 획득할 수 있다는 점은 복잡한 상태 머신을 설계할 때 필수적입니다. 또한 내부 최적화를 통해 파이썬 인터프리터 수준에서 루프 실행 속도가 소폭 향상되는 부수적인 이점도 있습니다.


5. 참고 문헌 및 자료 출처

  • Python Software Foundation. "PEP 380 -- Syntax for Delegating to a Subgenerator".
  • Luciano Ramalho. "Fluent Python: Clear, Concise, and Effective Programming". O'Reilly Media.
  • Python Documentation. "The yield statement & yield from expressions".
  • Real Python. "Introduction to Python Generators".
728x90