Я не понимаю, как и когда контекстный менеджер в недоделанном генераторе закрывается. Рассмотрим следующий менеджер контекста и функцию:
from contextlib import contextmanager
@contextmanager
def ctx():
print('enter ctx')
yield
print('exit ctx')
def gen_nums(n):
with ctx():
yield from range(n)
Моя первая интуиция заключалась в том, что если я вызову gen_nums
, но не использую генератор полностью, ctx
никогда не будет закрыт, что было довольно тревожно. Например:
for i, j in zip(range(5), gen_nums(10)):
print(f'{i}, {j}')
Здесь exit ctx
не печатается в конце. Насколько я понял, это означало, что если бы у меня был контекст файла в генераторе, он оставался бы открытым; однако затем я понял, что то же самое с файлами фактически правильно закроет файл. После некоторых тестов я узнал, что если я это сделаю:
from contextlib import contextmanager
@contextmanager
def ctx():
print('enter ctx')
try:
yield
finally:
print('exit ctx')
Теперь в конце было напечатано exit ctx
. Поэтому я предполагаю, что в какой-то момент будет вызвано какое-то исключение, но я не знаю, какое, где и когда (я пытался напечатать исключение с помощью except BaseException as e
, но это не сработало). Кажется, это происходит при удалении генератора, потому что если я делаю:
g = gen_nums(10)
for i, j in zip(range(5), g):
print(f'{i}, {j}')
del g
Тогда exit ctx
происходит только после del g
. Тем не менее, я хотел бы лучше понять, что здесь происходит и кто что запускает.
del g
, я подозреваю, что__del__
в некоторой степени эквивалентенg.close()
, который завершает работу генератора без StopIteration, чтобы разрешить освобождение ресурсов в диспетчере контекста. Следовательно, поскольку Генератор не выдает ошибки,ctx
нечего ловить вtry... except
. Однако если вы сделалиg.throw(SomeError)
во время его существования, вы увидитеctx
эту ошибку. - person r.ook   schedule 17.01.2019g
создается,ctx.__enter__()
запускается,g.send()
и т. д. во время работы генератора, а затем происходитg.close()
или эквивалентный, и возвращается кctx.__exit__()
(который принимает любую ошибку, которая была выдана). - person r.ook   schedule 17.01.2019stop
, и это вызовет исключениеGeneratorExit
, которое будет передано диспетчеру контекста. На самом деле, если я заменюfinally:
во второй версииctx
наexcept GeneratorExit:
, это тоже сработает (ранее я безуспешно пытался напечатать возбужденное исключение, потому что печатьGeneratorExit
ничего не показывает). - person jdehesa   schedule 18.01.2019c = ctx(); c.__enter__(); del c
, вы получите исключениеGeneratorExit
. На самом деле оба генератора@contextmanager
вызываютGeneratorExit
, когда они удаляются перед завершением, я просто не уверен, какой из них вызывается первым. - person jdehesa   schedule 18.01.2019c.__enter__()
, он жалуется на меняTypeError: '_GeneratorContextManager' object is not an iterator
. Я хотел получить некоторую ясность между тем, какой изctx
илиg
поднимаетGeneratorExit
yb, добавив некоторое описание, но я просто недостаточно хорошо разбираюсь, чтобы зайти так далеко. Возможно, придется создать собственныйCtx
с__enter__
__exit__
методами. Я не решаюсь дать наполовину ответ, который я сам не совсем понимаю, поэтому я оставил его в качестве комментария в надежде, что другие прояснят ваше решение. - person r.ook   schedule 18.01.2019