← Назад к вопросам

Возможно ли использовать несколько yield в генераторе на Python?

2.0 Middle🔥 81 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Множественные yield в генераторах Python

Да, полностью возможно и очень распространено использовать несколько yield в одном генераторе.

Что происходит с несколькими yield

Каждый yield выдает одно значение, и генератор паузируется в этом месте. Когда вызывается next(), выполнение продолжается со следующей строки после последнего yield.

def simple_generator():
    yield 1
    yield 2
    yield 3

# Как это работает:
gen = simple_generator()
print(next(gen))  # 1 (выполнился первый yield)
print(next(gen))  # 2 (выполнился второй yield)
print(next(gen))  # 3 (выполнился третий yield)
print(next(gen))  # StopIteration — генератор закончился

Визуализация потока выполнения

def generator():
    print("Start")
    yield 1           # ПАУЗИРУЕТ (точка 1)
    print("Middle")
    yield 2           # ПАУЗИРУЕТ (точка 2)
    print("End")
    yield 3           # ПАУЗИРУЕТ (точка 3)

gen = generator()
next(gen)      # Выводит "Start", потом yield 1
next(gen)      # Выводит "Middle", потом yield 2
next(gen)      # Выводит "End", потом yield 3
next(gen)      # StopIteration

Практические примеры

1. Итерация по диапазону значений

def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1

# Использование
for num in count_up_to(5):
    print(num)  # 1, 2, 3, 4, 5

2. Чтение файла построчно

def read_file_in_chunks(filename, chunk_size=1024):
    with open(filename, 'rb') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            yield chunk

# Использование (экономит память для больших файлов)
for chunk in read_file_in_chunks('large_file.bin', chunk_size=8192):
    process(chunk)

3. Генерация последовательности

def fibonacci(n):
    a, b = 0, 1
    count = 0
    while count < n:
        yield a
        a, b = b, a + b
        count += 1

# Использование
for num in fibonacci(7):
    print(num)  # 0, 1, 1, 2, 3, 5, 8

4. Фильтрация данных

def filter_numbers(numbers, min_val, max_val):
    for num in numbers:
        if min_val <= num <= max_val:
            yield num

# Использование
nums = [1, 5, 10, 15, 20, 25, 30]
for num in filter_numbers(nums, 10, 25):
    print(num)  # 10, 15, 20, 25

Yield с условиями

Очень частый паттерн — несколько yield в разных ветвях if/else:

def process_items(items):
    for item in items:
        if item > 0:
            yield f"positive: {item}"
        elif item < 0:
            yield f"negative: {item}"
        else:
            yield "zero"

# Использование
items = [1, -2, 0, 3, -4]
for result in process_items(items):
    print(result)
# positive: 1
# negative: -2
# zero
# positive: 3
# negative: -4

Yield в циклах

def flatten(nested_list):
    """Развернуть вложенные списки"""
    for item in nested_list:
        if isinstance(item, list):
            # Вложенный список — развернуть рекурсивно
            for sub_item in flatten(item):
                yield sub_item
        else:
            # Обычный элемент — выдать
            yield item

# Использование
nested = [1, [2, 3], [4, [5, 6]], 7]
for num in flatten(nested):
    print(num)  # 1, 2, 3, 4, 5, 6, 7

Yield в рекурсии (Tree traversal)

class TreeNode:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

def in_order_traversal(node):
    """Обход дерева в порядке in-order"""
    if node is None:
        return
    
    # Левое поддерево
    yield from in_order_traversal(node.left)
    
    # Сам узел
    yield node.value
    
    # Правое поддерево
    yield from in_order_traversal(node.right)

# Использование
root = TreeNode(
    2,
    TreeNode(1),
    TreeNode(3)
)

for val in in_order_traversal(root):
    print(val)  # 1, 2, 3

yield from (Python 3.3+)

Способ делегировать генерацию другому генератору:

def generator1():
    yield 1
    yield 2

def generator2():
    yield 3
    yield 4

def combined():
    yield from generator1()
    yield from generator2()

# Использование
for val in combined():
    print(val)  # 1, 2, 3, 4

# Эквивалент без yield from:
def combined_manual():
    for val in generator1():
        yield val
    for val in generator2():
        yield val

Отправка значений в генератор (send)

def echo():
    print("Generator started")
    value = yield "Ready"  # Первый yield
    print(f"Got: {value}")
    yield "Done"           # Второй yield

gen = echo()
print(next(gen))           # Generator started, выводит "Ready"
print(gen.send("Hello"))   # Got: Hello, выводит "Done"

# Нужно сначала вызвать next() или send(None)
# Иначе ошибка: "can't send non-None value to a just-started generator"

Production пример: парсинг больших файлов JSON

import json
from typing import Iterator, Dict, Any

def parse_large_json_stream(filename: str, batch_size: int = 100) -> Iterator[Dict[str, Any]]:
    """
    Читает большой JSON файл и выдает объекты по одному.
    Экономит память для файлов с миллионами строк.
    """
    batch = []
    
    with open(filename, 'r') as f:
        for line in f:
            try:
                obj = json.loads(line.strip())
                batch.append(obj)
                
                # Выдаем батч
                if len(batch) >= batch_size:
                    for item in batch:
                        yield item
                    batch = []
            except json.JSONDecodeError:
                continue
        
        # Выдаем оставшиеся элементы
        for item in batch:
            yield item

# Использование (без загрузки всего файла в память)
for record in parse_large_json_stream('millions_of_records.jsonl'):
    process_record(record)  # Обрабатываем по одному

Production пример: асинхронный генератор с несколькими yield

import asyncio
from typing import AsyncIterator

async def fetch_paginated_data(api_url: str) -> AsyncIterator[dict]:
    """Загружает данные со стра-по-страничного API"""
    page = 1
    
    while True:
        response = await fetch(f"{api_url}?page={page}")
        
        if not response.get('data'):
            break
        
        # Выдаем каждый элемент
        for item in response['data']:
            yield item
        
        # Проверяем, есть ли следующая страница
        if not response.get('has_next'):
            break
        
        page += 1

# Использование
async def main():
    async for item in fetch_paginated_data("https://api.example.com/items"):
        print(item)

asyncio.run(main())

Разница между return и yield

def with_yield():
    yield 1
    yield 2
    return "done"  # Возвращается как StopIteration.value

gen = with_yield()
print(next(gen))  # 1
print(next(gen))  # 2
try:
    print(next(gen))  # StopIteration
except StopIteration as e:
    print(f"Return value: {e.value}")  # Return value: done

# В цикле for возвращаемое значение не видно:
for val in with_yield():
    print(val)  # 1, 2 (return значение теряется)

Performance: Generator vs List

# ❌ List — загружает всё в память
def get_numbers_list():
    result = []
    for i in range(1_000_000):
        result.append(i)
    return result

# Память: ~40MB
data = get_numbers_list()

# ✅ Generator — ленивое вычисление
def get_numbers_generator():
    for i in range(1_000_000):
        yield i

# Память: ~1KB (только текущее значение)
for num in get_numbers_generator():
    pass

Когда использовать несколько yield

  • Потоковая обработка данных (файлы, API, БД)
  • Бесконечные последовательности (нельзя использовать list)
  • Экономия памяти на больших датасетах
  • Ленивое вычисление (вычисляется по требованию)
  • Pipeline обработки (несколько трансформаций)

Итог

Да, несколько yield в генераторе не только возможны, но и очень полезны. Генераторы — один из самых мощных инструментов Python для работы с большими данными и потоковой обработки.

В production коде я использую генераторы везде, где нужна обработка большого количества данных или I/O операции. Это экономит память и делает код более читаемым.