Поведение генератора с yield и throw в Python

Поведение генератора с yield и throw в Python

@python_quiz

Разберем этот квиз

Кратко: посмотрим, какие значения будут в списке logs после выполнения куска кода с генератором, yield внутри try/finally и вызовом gen.throw(...).

python

Пошаговый разбор выполнения

  1. Создается пустой список logs.
  2. Определение генератора g — функция ещё не выполнялась.
  3. gen = g() — создаётся объект-генератор, тело функции не выполнено.
  4. next(gen) — запуск генератора до первого yield:

- Выполняется logs.append('s') — теперь logs == ['s'].
- Выполняется выражение yield — генератор приостанавливается и возвращает управление вызвавшему коду.

  1. logs.append('mid') — добавляем 'mid', теперь logs == ['s', 'mid'].
  2. gen.throw(Exception('boom')) — бросаем исключение внутрь приостановленного генератора, оно "встает" в место yield:

- Исключение попадает внутрь генератора. В теле генератора нет обработки этого исключения, поэтому управление идёт к блоку finally.
- В finally выполняется logs.append('t') — теперь logs == ['s', 'mid', 't'].
- Поскольку исключение не было перехвачено внутри генератора, оно выходит наружу из gen.throw и попадает в внешнюю конструкцию try/except, где его ловят и подавляют.

  1. В итоге список logs содержит три элемента: 's', 'mid', 't'.

Важно: когда в yield бросают исключение и генератор не обрабатывает его, finally выполняется, после чего генератор закрывается и исключение передаётся вызывающему коду.

Демонстрация (можно запустить)

python

Анализ вариантов ответов

  • Вариант, где указан только ['s']: неверен. После next действительно будет 's', но в коде между next и throw есть append('mid'), который добавит 'mid' прежде чем будет брошено исключение.
  • Вариант с ['s', 'mid', 't']: соответствует фактическому поведению. next добавляет 's', затем вручную добавляется 'mid', а бросок исключения в yield приводит к выполнению finally и добавлению 't'.
  • Вариант с ['s', 't']: неверен, потому что 'mid' добавляется явно после next и до throw, так что он останется в списке.
  • Вариант пустого списка []: неверен — генератор создавался и стартовал, так что по крайней мере 's' добавится.

Частые ошибки и нюансы

  • Ожидание, что finally не выполнится при исключении — неверно: finally всегда выполнится при выходе из блока try, даже если выход происходит из-за исключения, попавшего в yield.
  • Разница между исключением, брошенным внутри генератора (и обработанным там), и исключением, которое не обработано: в первом случае генератор продолжит работу, во втором — завершится и передаст исключение вызвавшему.
  • gen.throw не только вызывает исключение, но и может возвращать значение, если внутри генератора исключение перехвачено и затем yield вернёт значение — это более продвинутые сценарии.

Вывод

После выполнения кода в списке logs окажутся три элемента в порядке: 's', 'mid', 't'. Причина: next запускает генератор до yield (добавляя 's'), затем явно добавляется 'mid', а gen.throw пробрасывает исключение в место yield, что вызывает выполнение finally и добавление 't'.

Report Page