История — в Python для управления ресурсами (файлы, соединения, транзакции) используется конструкция with, основанная на протоколе контекстных менеджеров (enter, exit). Для простых случаев писать целый класс избыточно, поэтому был предложен декоратор @contextmanager (модуль contextlib), позволяющий определять менеджеры ресурсов как генераторы.
Проблема — вручную освобождать или закрывать ресурсы неудобно, код становится неустойчивым к ошибкам (например, забыли закрыть файл). Также не хочется ради простых вещей (например, временной смены каталога или stdout) писать отдельный класс с двумя методами.
Решение — использовать @contextmanager, чтобы лаконично описать "начало" и "конец" использования ресурса, гарантированно обработав исключения и освобождение.
Пример кода:
from contextlib import contextmanager @contextmanager def open_file(filename, mode): f = open(filename, mode) try: yield f finally: f.close() with open_file('test.txt', 'w') as f: f.write('Hello')
Ключевые особенности:
Можно ли сделать так, чтобы в yield из @contextmanager возвращалось несколько объектов (например, через кортеж)?
Да, можно и даже удобно для возврата "группы" связанных ресурсов.
Пример кода:
@contextmanager def managed_two(): a, b = [], {} try: yield a, b finally: a.clear(); b.clear()
Что будет, если после yield выбросить исключение — закроется ли ресурс?
Да, finally блок исполнится в любом случае, даже если из кода внутри with возникнет ошибка/исключение.
Может ли @contextmanager заменить полноценный класс-контекстный менеджер на enter/exit?
В большинстве тривиальных случаев — да, для более сложных с вложенными состояниями или наследованием работать удобнее через класс.
Вручную открывают и закрывают файл:
f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()
Плюсы:
Минусы:
Используют @contextmanager для временной смены рабочего каталога или открытия файла (или настроек среды):
@contextmanager def work_in(dirname): import os prev = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(prev)
Плюсы:
Минусы: