Есть ли осмысленный способ использовать контекстные менеджеры внутри генераторов?

#python #generator #contextmanager

#python #генератор #contextmanager

Вопрос:

 from contextlib import contextmanager

@contextmanager
def context():
    print "entering"
    yield
    print "exiting"

def test():
    with context():
        for x in range(10):
            yield x

for x in test():
    if x == 5:
        break  # or raise
  

вывод:

 entering
  

Есть ли способ заставить python автоматически вызывать __exit__ метод context() при прерывании for цикла? Или какой-то другой способ достижения той же цели? То, что я знаю о генераторах и контекстных менеджерах, заставляет меня подозревать, что это невозможно, но это делает контекстные менеджеры довольно бесполезными внутри генераторов, не так ли? Мне кажется, yield инструкция внутри with блока должна вызывать красный флаг, context manager __exit__ может не запускаться.

Ответ №1:

Ну, вы могли бы обернуть функцию yield в context() предложением try / finally:

 from contextlib import contextmanager

@contextmanager
def context():
    print "entering"
    try:
        yield
    finally:
        print "exiting"

def test():
    with context():
        for x in range(10):
            yield x

for x in test():
    if x == 5:
        break  # or raise
  

вывод:

 entering
exiting
  

Редактировать: Если вы попробуете: help(contextmanager), он покажет, что это «типичный» пример использования, где они завершают yield предложением try / finally .

Комментарии:

1. Это именно то, что вы должны делать: после того, как цикл test() будет удален, который возникает GeneratorExit() при yield x . Это, в свою очередь, выходит из with: for: блока. Затем contextmanager GeneratorExit() снова перехватывает и выводит его с выходом в context — и вот где вы должны его перехватить, иначе context все заканчивается прямо там (в конце концов, это тоже генератор, поэтому это исключение завершает его без выполнения очистки). В документах указано, что: «Таким образом, вы можете использовать оператор try … except … finally для исправления ошибки (если таковая имеется) или обеспечения выполнения некоторой очистки»