Назначение функции 'send' в генераторах Python: примеры использования

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Основная функция метода send() заключается в передаче значений в генератор на стадии yield, чем обеспечивается возможность регулирования их поведения в процессе выполнения. Это отличает его от метода next(), который лишь продвигает генератор к следующему значению yield.

Посмотрим пример:

Python
Скопировать код
def countdown(start):
    current = start
    while current > 0:
        received = (yield current)
        current = received if received is not None else current – 1

gen = countdown(5)
print(next(gen))    # напечатает 5. Запускаем обратный отсчёт.
print(gen.send(3))  # напечатает 3. Это нечто вроде фокуса!
print(next(gen))    # напечатает 2. Продолжаем обратный отсчёт.

Из этого примера становится ясно, что метод gen.send(3) перезапускает отсчёт со значения 3. Таким образом демонстрируется динамическое управление, к которому позволяет прийти send().

Кинга Идем в IT: пошаговый план для смены профессии

Применение send для упрощения асинхронного кода

В контексте асинхронных операций, в частности с использованием фреймворков типа Twisted, метод send может превратить код в более читаемый и менее запутанный. С применением декоратора @defer.inlineCallbacks и метода send, есть возможность управлять "определёнными" значениями (Deferred values), избегая при этом сложностей, связанных с callback'ами. Вот так выглядит пример, в котором send упрощает структуру асинхронного кода и дает возможность корутинам сгенерировать и получить результаты на лету.

Python
Скопировать код
@defer.inlineCallbacks
def async_operation():
    result = yield some_deferred_operation()
    print('Операция выполнена:', result)
    new_result = yield another_deferred_operation(result)
    defer.returnValue(new_result)

в этом случае yield внутри @defer.inlineCallbacks автоматически обрабатывается с Deferreds, что делает возможным написание более понятного и линейного кода, упрощая тем самым понимание асинхронных операций.

send: Двухсторонний канал связи

Метод send() создаёт двусторонний канал связи между генераторами. Эта двухсторонняя связь делает генераторы корутинами, оптимизированными для асинхронных операций ввода-вывода.

Python
Скопировать код
def user_input_processor():
    result = ""
    while True:
        user_input = (yield result)
        result = f"Обработано: {user_input}"

processor = user_input_processor()
next(processor)  # Инициализация процессора
print(processor.send("Привет"))  # напечатает: "Обработано: Привет"
print(processor.send("Мир!"))    # напечатает: "Обработано: Мир!"

Отсюда видно, как send реально меняет поведение генератора на основе введённых данных, делая генераторы отзывчивыми и адаптируемыми.

Изменение поведения в реальном времени с помощью send

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

Python
Скопировать код
def traffic_light():
    color = 'Зелёный'
    while True:
        color = (yield color)
        color = 'Красный' if color == 'Жёлтый' else 'Зелёный'

light = traffic_light()
print(next(light))        # напечатает 'Зелёный'
print(light.send('Жёлтый')) # напечатает 'Красный'. Стоп!

Визуализация

Проиллюстрируем взаимодействие с генератором Python используя функцию send:

Представим игровой контроллер (🎮), который управляет персонажем (🧍):

Python
Скопировать код
def game_character():
    action = yield "Жду команд от игрока..."
    while True:
        if action == "MOVE":
            yield "Персонаж идёт!"
        elif action == "JUMP":
            yield "Персонаж прыгает!"
        else:
            yield "Персонаж не понимает действие..."

Каждое нажатие кнопки на 🎮 приводит к выполнению команды для персонажа 🧍:

Markdown
Скопировать код
🎮 `.send("JUMP")`: Персонаж прыгает! Вот что значит прыгун!
🎮 `.send("MOVE")`: Персонаж идёт! Начинаем путь, пока играет мелодия.
🎮 `.send(None)`:   Персонаж не понимает действие... Где инструкция?

Действия персонажа контролируются через send(), что обеспечивает присутствие интерактивности в игре.

Поддержка состояния с помощью "send"

Генераторы, использующие send, способны хранить своё состояние между операторами yield на протяжении всего своего жизненного цикла. Эта особенность позволяет реализовать такие варианты использования, как подсчёт промежуточной суммы или машины состояний, что непостижимо для традиционных методов:

Python
Скопировать код
def running_total():
    total = 0
    while True:
        addend = (yield total)
        total += addend if addend is not None else 0

accumulator = running_total()
next(accumulator)  # готовим аккумулятор
print(accumulator.send(5))    # прибавляем 5; напечатает 5
print(accumulator.send(10))   # прибавляем 10; напечатает 15

Здесь через send() к ранее вычисленной сумме аккумулятора добавляются новые значения.

Интерактивные шаблоны с использованием “send”

Сочетание методов send() и yield позволяет создать интерактивные шаблоны генераторов. Это уже не просто пассивные производители последовательностей. Генераторы могут динамически отвечать на внешние команды:

Python
Скопировать код
def interactive_quiz():
    score = 0
    question = (yield f"Счёт: {score}. Давайте начнём: сколько будет два плюс два?")
    while True:
        if question == "4":
            score += 1
            question = (yield f"Отлично! Счёт: {score}. Следующий вопрос: какой город является столицей Франции?")
        else:
            question = (yield f"Неверно. Счёт: {score}. Попробуйте ещё раз!")
quizinator = interactive_quiz()
next(quizinator)  # Задаём первый вопрос
print(quizinator.send("4"))  # Последует следующий вопрос

Здесь генератор функционирует как простейшая интерактивная викторина, обладающая способностью к самостоятельному взаимодействию.

Полезные материалы

  1. PEP 342 – Реализация сопрограмм с помощью расширенных генераторов — ключевое предложение, которое описывает, как send расширяет возможности генераторов в Python.
  2. 6. Выражения — Официальная документация Python 3официальная документация Python, где подробно описаны yield выражения в контексте генераторов.
  3. Сопрограммы и задачи — Официальная документация Python 3 — подробности о сопрограммах asyncio и связанных с ними задачах для параллельного выполнения кода.
  4. Генераторы – Python Wikiобзор возможностей генераторов с выделением описания метода send.
  5. Эффективный Python: 90 специфических рекомендаций для написания более качественного кода на Python — советы касательно эффективного использования итераторов и генераторов для улучшения кода на Python.