GeneratorExit
повышается каждый раз, когда закрывается генератор или сопрограмма.Даже без менеджера контекста мы можем воспроизвести точное условие с помощью простой функции генератора, которая выводит информацию об исключительной ситуации при возникновении ошибок (дальнейшее сокращение предоставленного кода, чтобы точно показать, как и где генерируется это исключение).
import sys
def dummy_gen():
for idx in range(5):
try:
yield idx
except:
print(sys.exc_info())
raise
for i in dummy_gen():
raise ValueError('foo')
Использование:
(<class 'GeneratorExit'>, GeneratorExit(), <traceback object at 0x7f96b26b4cc8>)
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: foo
Обратите внимание, что было также исключение, которое было вызвано внутри самого генератора, поскольку отмечалось, что был выполнен блок except
.Обратите внимание, что исключение было также далее raise
'd после оператора print, но обратите внимание, что это на самом деле нигде не показано, потому что оно обрабатывается внутри.
Мы также можем злоупотребить этим фактом, чтобы увидеть, можем ли мыуправляйте потоком, проглотив исключение GeneratorExit
и посмотрите, что произойдет.Это можно сделать, удалив оператор raise
внутри функции dummy_gen
, чтобы получить следующий вывод:
(<class 'GeneratorExit'>, GeneratorExit(), <traceback object at 0x7fd1f0438dc8>)
Exception ignored in: <generator object dummy_gen at 0x7fd1f0436518>
RuntimeError: generator ignored GeneratorExit
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: foo
Обратите внимание, что был вызван внутренний RuntimeError
, который жаловался на игнорирование генераторафункция GeneratorExit
.Таким образом, из этого мы можем ясно видеть, что это исключение создается самим генератором внутри функции генератора, и ValueError
, который поднимается за пределами , что никогда не присутствует в области видимости внутри .функция генератора.
Поскольку диспетчер контекста будет перехватывать все исключения как есть, а менеджер контекста внутри функции генератора, все возникающие внутри него исключения будут просто передаваться в __exit__
какявляется.Рассмотрим следующее:
class Context(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
logging.error("Aborted %s", self,
exc_info=(exc_type, exc_value, traceback))
Измените dummy_gen
на следующее:
def dummy_gen():
with Context():
for idx in range(5):
try:
yield idx
except:
print(sys.exc_info())
raise
Выполните полученный код:
(<class 'GeneratorExit'>, GeneratorExit(), <traceback object at 0x7f44b8fb8908>)
ERROR:root:Aborted <__main__.Context object at 0x7f44b9032d30>
Traceback (most recent call last):
File "foo.py", line 26, in dummy_gen
yield idx
GeneratorExit
Traceback (most recent call last):
File "foo.py", line 41, in <module>
raise ValueError('foo')
ValueError: foo
То же самое GeneratorExit
то, что поднято, теперь представляется диспетчеру контекста, потому что это поведение, которое было определено.