Подтвердить что ты не робот

Что такое generator.throw() хорошо для?

PEP 342 (Coroutines через Enhanced Generators) добавил метод throw() для объектов генератора, что позволяет вызывающему абоненту создавать исключение внутри генератор (как если бы оно было выбрано выражением yield).

Мне интересно, какие варианты использования для этой функции есть.

4b9b3361

Ответ 1

Скажем, я использую генератор для обработки добавления информации в базу данных; Я использую это для хранения информации, полученной в сети, и, используя генератор, я могу делать это эффективно всякий раз, когда я действительно получаю данные, и делаю другие вещи иначе.

Итак, мой генератор сначала открывает соединение с базой данных, и каждый раз, когда вы его отправляете, он добавляет строку:

def add_to_database(connection_string):
    db = mydatabaselibrary.connect(connection_string)
    cursor = db.cursor()
    while True:
        row = yield
        cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row)

Все хорошо и хорошо; каждый раз, когда я .send() мои данные, он вставляет строку.

Но что делать, если моя база данных является транзакционной? Как я могу сигнализировать об этом генераторе, когда фиксировать данные в базе данных? А когда прекратить транзакцию? Более того, он поддерживает открытое соединение с базой данных, возможно, я иногда хочу, чтобы он закрыл это соединение для восстановления ресурсов.

Здесь приходит метод .throw(); с .throw() Я могу вызывать исключения в этом методе, чтобы сигнализировать об определенных обстоятельствах:

def add_to_database(connection_string):
    db = mydatabaselibrary.connect(connection_string)
    cursor = db.cursor()
    try:
        while True:
            try:
                row = yield
                cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row)
            except CommitException:
                cursor.execute('COMMIT')
            except AbortException:
                cursor.execute('ABORT')
    finally:
        cursor.execute('ABORT')
        db.close()

Метод .close() на генераторе делает по существу одно и то же; он использует исключение GeneratorExit в сочетании с .throw(), чтобы закрыть работающий генератор.

Все это является важной основой работы сопрограмм; coroutines являются, по существу, генераторами, а также дополнительным синтаксисом для упрощения и четкости написания сопрограммы. Но под капотом они все еще строятся на том же уступке и отправке. И когда вы выполняете параллельные параллельные параллельные параллели, вам нужен способ для чистого выхода из этих сопрограмм, если один из них потерпел неудачу, просто чтобы назвать пример.

Ответ 2

По моему мнению, метод throw() полезен по многим причинам.

  • Симметрия: нет сильной причины, по которой исключительное условие должно обрабатываться только в вызывающем, а не в функции генератора. (Предположим, что значения чтения генератора из базы данных возвращают плохое значение и предположим, что только вызывающий абонент знает, что это значение плохо. С помощью метода throw() вызывающий может сигнализировать генератору, что есть ненормальная ситуация, которая должна быть скорректированным.) Если генератор может вызвать исключение, перехваченное вызывающим абонентом, также должно быть возможно обратное.

  • Гибкость: функция генератора может иметь более одного оператора yield, и вызывающий может не знать о внутреннем состоянии генератора. Исключением может быть reset генератор в известное состояние или реализовать более сложное управление потоком, которое было бы более громоздким с помощью next(), send(), close().

Запрос на использование вариантов может вводить в заблуждение: для каждого варианта использования вы можете создать встречный пример без использования метода throw(), и обсуждение продолжится навсегда...

Ответ 3

Одним из вариантов использования является включение информации о внутреннем состоянии генератора в трассировке стека при возникновении исключения - информации, которая в противном случае не была бы видима вызывающему.

Например, скажем, у нас есть генератор, такой как следующее, где внутреннее состояние, которое мы хотим, это текущий номер индекса генератора:

def gen_items():
    for i, item in enumerate(["", "foo", "", "foo", "bad"]):
        if not item:
            continue
        try:
            yield item
        except Exception:
            raise Exception("error during index: %d" % i)

Следующий код недостаточно для запуска дополнительной обработки исключений:

# Stack trace includes only: "ValueError: bad value"
for item in gen_items():
    if item == "bad":
        raise ValueError("bad value")

Однако следующий код обеспечивает внутреннее состояние:

# Stack trace also includes: "Exception: error during index: 4"
gen = item_generator()
for item in gen:
    if item == "bad":
        gen.throw(ValueError, "bad value")

Ответ 4

Этот "ответ" больше похож на мелочи.

Мы можем (ab) использовать генератор throw(), чтобы вызвать Exception внутри лямбды, которая иначе не поддерживает оператор raise.

foo = lambda: (_ for _ in ()).throw(Exception('foobar'))

Цитируется из fooobar.com/info/76277/...