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

Как получить значение в Python генераторе, посылаемое через метод send()?

2.0 Middle🔥 101 комментариев
#Python Core#Асинхронность и многопоточность

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

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

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

Получение значений через send() в генераторах Python

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

Базовый механизм

Как работает send()

def simple_generator():
    # Ожидаем первого значения
    x = yield "First pause"
    print(f"Получили значение: {x}")
    
    # Ожидаем второго значения
    y = yield "Second pause"
    print(f"Получили второе значение: {y}")
    
    return "Generator finished"

# Создаём генератор
gen = simple_generator()

# Первый вызов next() или send(None)
# Выполняется до первого yield
result1 = next(gen)
print(f"Результат 1: {result1}")  # Выведет: "First pause"

# Отправляем значение 42 в генератор
# Это становится результатом выражения x = yield "First pause"
result2 = gen.send(42)
print(f"Получили значение: 42")  # От print в генераторе
print(f"Результат 2: {result2}")  # Выведет: "Second pause"

# Отправляем второе значение
result3 = gen.send(100)
print(f"Получили второе значение: 100")
# Генератор завершился

Вывод:

Результат 1: First pause
Получили значение: 42
Результат 2: Second pause
Получили второе значение: 100

Важное уточнение: первый вызов

def example():
    value = yield "Start"
    print(f"Value: {value}")

gen = example()

# ❌ ОШИБКА - не можно отправить значение до первого yield
try:
    gen.send(42)  # TypeError!
except TypeError as e:
    print(f"Ошибка: {e}")
    # TypeError: can't send non-None value to a just-started generator

# ✅ ПРАВИЛЬНО - сначала next() или send(None)
gen = example()
result = gen.send(None)  # Или next(gen)
print(f"Result: {result}")  # "Start"

# Теперь отправляем
gen.send(42)

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

1. Двусторонний обмен данными

def echo_generator():
    """Генератор эхо - возвращает то, что ему отправляют"""
    while True:
        x = yield
        print(f"Эхо: {x}")

gen = echo_generator()
next(gen)  # Инициализируем

gen.send("Hello")
gen.send("World")
gen.send(123)

# Вывод:
# Эхо: Hello
# Эхо: World
# Эхо: 123

2. Потребитель-производитель

def producer():
    """Производитель - отправляет данные потребителю"""
    for i in range(1, 6):
        result = yield i
        print(f"Потребитель обработал: {result}")

def consumer():
    """Потребитель - обрабатывает данные"""
    consumed = 0
    while True:
        data = yield f"Обработано {consumed} элементов"
        if data:
            consumed += 1
            print(f"Потребитель получил: {data}")

# Использование
prod = producer()
cons = consumer()

# Инициализируем оба
print(next(prod))
print(next(cons))

# Производитель отправляет данные потребителю
for value in [1, 2, 3, 4, 5]:
    print(f"\nПроизводитель отправляет: {value}")
    result = prod.send(value)
    print(f"Потребитель ответит: {result}")
    result = cons.send(value)

3. Асинхронный callback паттерн

def fetch_user_data(user_id: int):
    """Генератор для асинхронного получения данных"""
    print(f"Запрашиваю данные пользователя {user_id}")
    
    # yield останавливает генератор и возвращает запрос
    response = yield f"GET /users/{user_id}"
    
    # После send() получаем ответ
    user_data = response
    print(f"Получены данные: {user_data}")
    
    # Обрабатываем данные
    return {"user": user_data, "processed": True}

# Симуляция фреймворка (как asyncio)
def run_generator(gen):
    """Симуляция event loop"""
    request = None
    while True:
        try:
            if request is None:
                request = next(gen)
            else:
                # request - это URL который нужно получить
                # Симулируем HTTP запрос
                response = {"id": 1, "name": "Alice", "email": "alice@example.com"}
                request = gen.send(response)
        except StopIteration as e:
            return e.value

# Запуск
gen = fetch_user_data(1)
result = run_generator(gen)
print(f"Результат: {result}")

# Вывод:
# Запрашиваю данные пользователя 1
# Получены данные: {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}
# Результат: {'user': {...}, 'processed': True}

Обработка ошибок с throw()

def error_handler():
    try:
        x = yield "Waiting"
        print(f"Получили: {x}")
        
        y = yield "Waiting again"
        print(f"Получили: {y}")
    except ValueError as e:
        print(f"Поймана ошибка: {e}")
        yield "Error handled"

gen = error_handler()
print(next(gen))  # "Waiting"
print(gen.send(42))  # "Waiting again"

# Отправляем исключение
print(gen.throw(ValueError("Что-то пошло не так")))
# Выведет:
# Поймана ошибка: Что-то пошло не так
# Error handled

Связь yield и return в генераторе

def generator_with_return():
    yield 1
    yield 2
    return "Done"  # Значение попадает в StopIteration.value

gen = generator_with_return()
print(next(gen))  # 1
print(next(gen))  # 2

try:
    next(gen)
except StopIteration as e:
    print(f"Возвращённое значение: {e.value}")  # "Done"

# Или с send()
gen = generator_with_return()
print(gen.send(None))  # 1
print(gen.send(None))  # 2
try:
    gen.send(None)
except StopIteration as e:
    print(f"Возвращённое значение: {e.value}")

Корутины и двусторонняя коммуникация

def coroutine_sum():
    """Корутина - складывает отправляемые значения"""
    total = 0
    while True:
        x = yield total  # Отправляем текущее значение
        if x is None:
            break
        total += x
    return total

# Использование
gen = coroutine_sum()
print(gen.send(None))  # 0 (инициализация)
print(gen.send(10))    # 10
print(gen.send(20))    # 30
print(gen.send(5))     # 35

try:
    gen.send(None)  # Выход из цикла
except StopIteration as e:
    print(f"Сумма: {e.value}")  # 35

Практическое применение

Пайпайн обработки данных

def filter_even():
    """Фильтр - пропускает только чётные числа"""
    while True:
        x = yield
        if x and x % 2 == 0:
            print(f"Чётное: {x}")

def double():
    """Трансформация - удваивает числа"""
    while True:
        x = yield
        if x:
            print(f"Удвоенное: {x * 2}")

# Создание пайпайна
filter_gen = filter_even()
double_gen = double()

next(filter_gen)
next(double_gen)

# Отправляем данные
for num in range(1, 6):
    print(f"\nОтправляем: {num}")
    filter_gen.send(num)
    double_gen.send(num)

# Вывод:
# Отправляем: 1
# Отправляем: 2
# Чётное: 2
# Удвоенное: 4
# ...

Жизненный цикл генератора с send()

1. gen = generator()  - создание
2. next(gen) или gen.send(None) - инициализация, выполнение до первого yield
3. gen.send(value) - отправка значения, выполнение до следующего yield
4. gen.throw(exc) - отправка исключения
5. gen.close() - закрытие генератора

Когда использовать send()

  • Корутины - двусторонний обмен данными
  • Пайпайны - обработка потоков данных
  • Фреймворки - asyncio использует send() для управления
  • Симуляции - event-driven системы

Современная альтернатива: async/await

В Python 3.5+ вместо генераторов с send() используют async/await:

async def fetch_data(user_id):
    response = await fetch_user(user_id)  # Вместо yield
    return response

Это выглядит понятнее, но на уровне CPython использует тот же механизм.

Итоговый пример: Состояние машины

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

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

Метод send() - это ключ к пониманию Python корутин и асинхронного программирования!