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

Зачем генератору метод send?

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

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

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

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

Зачем генератору метод send

Метод send() - это механизм двусторонней коммуникации между генератором и кодом, который его вызывает. Это позволяет передавать данные ВНУТРЬ генератора, а не просто получать данные ОТ генератора.

Основное назначение send()

Обычно генератор только возвращает значения через yield. Метод send() позволяет отправить значение обратно в генератор в точку, где он остановился.

# Простой генератор БЕЗ send()
def simple_generator():
    for i in range(3):
        yield i

gen = simple_generator()
print(next(gen))  # 0
print(next(gen))  # 1
print(next(gen))  # 2

# Генератор С send() - двусторонняя коммуникация
def echo_generator():
    message = "Инициализация"
    print(f"Генератор начался: {message}")
    
    while True:
        received = yield message  # Возвращаем message и ждём значения
        message = f"Получено: {received}"
        print(message)

gen = echo_generator()
print(next(gen))           # Инициализация (стартуем генератор)
print(gen.send("Hello"))   # Hello попадает в переменную received
print(gen.send("World"))   # World попадает в переменную received

Как это работает: пошагово

def counter_with_send():
    count = 0
    print("[Gen] Генератор начал работу")
    
    while True:
        increment = yield count  # Возвращаем count и ждём значения
        print(f"[Gen] Получил: {increment}")
        
        if increment is None:
            count += 1
        else:
            count += increment

gen = counter_with_send()

print("[Main] Вызываем next()")
result = next(gen)  # Эквивалентно gen.send(None)
print(f"[Main] Получил из генератора: {result}")  # 0

print("\n[Main] Отправляем 5")
result = gen.send(5)  # 5 попадает в переменную increment
print(f"[Main] Получил из генератора: {result}")  # 6

print("\n[Main] Отправляем 3")
result = gen.send(3)
print(f"[Main] Получил из генератора: {result}")  # 9

Вывод:

[Gen] Генератор начал работу
[Main] Вызываем next()
[Main] Получил из генератора: 0

[Main] Отправляем 5
[Gen] Получил: 5
[Main] Получил из генератора: 6

[Main] Отправляем 3
[Gen] Получил: 3
[Main] Получил из генератора: 9

Практический пример: обработчик команд

def command_processor():
    """Генератор, обрабатывающий команды"""
    print("Процессор готов получать команды")
    
    results = []
    
    while True:
        command = yield results.copy()  # Возвращаем текущие результаты
        
        if command is None:
            continue
        
        action, value = command
        
        if action == "add":
            results.append(value)
            print(f"Добавили: {value}")
        elif action == "remove":
            if value in results:
                results.remove(value)
                print(f"Удалили: {value}")
        elif action == "clear":
            results.clear()
            print("Очистили список")

proc = command_processor()

print("1. Инициализируем:")
print(next(proc))  # []

print("\n2. Добавляем элементы:")
print(proc.send(("add", 10)))   # [10]
print(proc.send(("add", 20)))   # [10, 20]
print(proc.send(("add", 30)))   # [10, 20, 30]

print("\n3. Удаляем:")
print(proc.send(("remove", 20)))  # [10, 30]

print("\n4. Очищаем:")
print(proc.send(("clear",)))     # []

Реальный пример: корутины для обработки данных

def averager():
    """Вычисляет скользящее среднее"""
    total = 0.0
    count = 0
    average = None
    
    while True:
        value = yield average  # Возвращаем текущее среднее
        if value is not None:
            total += value
            count += 1
            average = total / count

# Используем генератор для вычисления среднего
gen = averager()
next(gen)  # Инициализируем

print(gen.send(10))   # (10 + 0) / 1 = 10.0
print(gen.send(20))   # (10 + 20) / 2 = 15.0
print(gen.send(30))   # (10 + 20 + 30) / 3 = 20.0
print(gen.send(40))   # (10 + 20 + 30 + 40) / 4 = 25.0

Конвейер обработки данных

Одно из самых мощных применений send() - создание конвейеров.

def producer():
    """Генерирует числа и отправляет в pipeline"""
    for i in range(1, 6):
        print(f"[Producer] Генерирую {i}")
        yield i

def doubler(downstream):
    """Удваивает значения и передаёт дальше"""
    while True:
        x = yield
        print(f"[Doubler] Удваиваю {x} -> {x * 2}")
        downstream.send(x * 2)

def adder(downstream):
    """Прибавляет 10 и передаёт дальше"""
    while True:
        x = yield
        print(f"[Adder] Прибавляю 10: {x} -> {x + 10}")
        downstream.send(x + 10)

def printer():
    """Конечный обработчик - печатает результат"""
    while True:
        x = yield
        print(f"[Printer] Финальное значение: {x}")

# Строим конвейер
p = printer()
a = adder(p)
d = doubler(a)

next(p)   # Инициализируем все генераторы
next(a)
next(d)

# Запускаем
for value in producer():
    d.send(value)  # Отправляем в начало конвейера

print("\nЛогика конвейера:")
print("1 -> *2 = 2")
print("2 -> +10 = 12")
print("12 -> print")

Отправка исключений: throw()

Максимум send() - это также метод throw() для отправки исключений.

def fault_tolerant_gen():
    try:
        while True:
            x = yield
            print(f"Обработал: {x}")
    except ValueError as e:
        print(f"Перехватил ошибку: {e}")
        print("Продолжаю работу")

gen = fault_tolerant_gen()
next(gen)

gen.send(10)  # Обработал: 10
gen.send(20)  # Обработал: 20

try:
    gen.throw(ValueError, ValueError("Что-то пошло не так!"))
except StopIteration:
    pass

gen.send(30)  # Обработал: 30

close() - остановка генератора

def cleanup_gen():
    try:
        while True:
            x = yield
            print(f"Обработал: {x}")
    finally:
        print("Генератор закрывается, выполняем cleanup")
        # Освобождаем ресурсы

gen = cleanup_gen()
next(gen)

gen.send(10)  # Обработал: 10
gen.send(20)  # Обработал: 20

gen.close()  # Генератор закрывается, выполняем cleanup

Зачем это нужно на практике

1. Асинхронное программирование (был основой asyncio в Python 3.3-3.4)

def fetch_data(url):
    """Корутина для загрузки данных"""
    response = yield Request(url)  # Отправляем запрос
    return response  # Получаем результат обратно через send()

def scheduler():
    """Простой scheduler для корутин"""
    tasks = [fetch_data("http://example.com")]
    
    while tasks:
        for task in tasks:
            try:
                request = next(task)  # или task.send(result)
                # Обрабатываем request
                result = do_request(request)
                task.send(result)  # Отправляем результат обратно
            except StopIteration:
                tasks.remove(task)

2. State machines (конечные автоматы)

def state_machine():
    state = "idle"
    
    while True:
        event = yield state
        
        if state == "idle" and event == "start":
            state = "running"
        elif state == "running" and event == "stop":
            state = "idle"
        elif state == "running" and event == "pause":
            state = "paused"
        elif state == "paused" and event == "resume":
            state = "running"

machine = state_machine()
print(next(machine))           # idle
print(machine.send("start"))   # running
print(machine.send("pause"))   # paused
print(machine.send("resume"))  # running
print(machine.send("stop"))    # idle

Сравнение подходов

# БЕЗ send() - только получение данных
def fibonacci_gen():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# С send() - двусторонняя коммуникация
def interactive_fibonacci():
    a, b = 0, 1
    while True:
        command = yield a  # Возвращаем текущее значение и ждём команды
        if command == "next":
            a, b = b, a + b
        elif command == "reset":
            a, b = 0, 1

Важные моменты

  1. Первый вызов должен быть next() или send(None)

    gen = my_generator()
    next(gen)  # Инициализируем
    # Теперь можно вызывать send()
    
  2. Значение, переданное send(), становится результатом выражения yield

    value = yield  # value будет равна аргументу send()
    
  3. send(None) эквивалентен next()

    next(gen) == gen.send(None)
    

Вывод

Метод send() превращает генератор из одностороннего источника данных в полноценную корутину с двусторонней коммуникацией. Это мощный инструмент для:

  • Создания асинхронного кода
  • Реализации конечных автоматов
  • Построения конвейеров обработки данных
  • Управления состоянием

Хотя современный Python предпочитает async/await вместо корутин с send(), понимание этого механизма критично для глубокого знания языка и его истории.