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

Генератор Python, исключение без проглатывания в 'coroutine'

Недавно я встретил какое-то удивительное поведение в генераторах Python:

class YieldOne:
  def __iter__(self):
    try:
      yield 1
    except:
      print '*Excepted Successfully*'
      # raise

for i in YieldOne():
  raise Exception('test exception')

Что дает результат:

*Excepted Successfully*
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: test exception

Я был (приятно) удивлен, что *Excepted Successfully* получил распечатку, так как это было то, что я хотел, но также удивился тому, что Exception до сих пор распространялся до верхнего уровня. Я ожидал использовать ключевое слово raise (прокомментированное в этом примере), чтобы получить наблюдаемое поведение.

Может кто-нибудь объяснить, почему эта функция работает так, как она есть, и почему except в генераторе не проглатывает исключение?

Является ли это единственным экземпляром в Python, где except не проглатывает исключение?

4b9b3361

Ответ 1

Ваш код не работает так, как вы думаете. Вы не можете создавать Исключения в сопрограмме, подобной этому. Вместо этого вы выбираете исключение GeneratorExit. Посмотрите, что произойдет, если вы используете другое исключение:

class YieldOne:
  def __iter__(self):
    try:
      yield 1
    except RuntimeError:
        print "you won't see this"
    except GeneratorExit:
      print 'this is what you saw before'
      # raise

for i in YieldOne():
  raise RuntimeError

По мере того как это все еще получает upvotes, вот как вы создаете исключение в генераторе:

class YieldOne:
  def __iter__(self):
    try:
      yield 1
    except Exception as e:
      print "Got a", repr(e)
      yield 2
      # raise

gen = iter(YieldOne())

for row in gen:
    print row # we are at `yield 1`
    print gen.throw(Exception) # throw there and go to `yield 2` 

Смотрите документы для generator.throw.

Ответ 2

EDIT: что сказал THC4k.

Если вы действительно хотите создать произвольное исключение внутри генератора, используйте метод throw:

>>> def Gen():
...     try:
...             yield 1
...     except Exception:
...             print "Excepted."
...
>>> foo = Gen()
>>> next(foo)
1
>>> foo.throw(Exception())
Excepted.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Вы заметите, что на верхнем уровне вы получите StopIteration. Они поднимаются генераторами, которые исчерпали элементы; они обычно проглатываются контуром for, но в этом случае генератор генерирует исключение, поэтому цикл их не замечает.