2.24. Менеджеры контекста¶
Оператор with выполняет код настройки, затем тело, затем код завершения – с гарантией, что код завершения выполнится, даже если тело завершится с ошибкой на полпути. Пара методов, обеспечивающая настройку и завершение, называется менеджером контекста.
Форма такова:
with <expression> as <name>:
<body>
Выражение возвращает менеджер контекста. Python вызывает его метод __enter__, при необходимости связывает результат с <name>, выполняет тело, а затем вызывает __exit__ – независимо от того, завершилось ли тело нормально или возбудило исключение.
__exit__ выполняется в конце блока, что бы внутри него ни происходило.¶
2.24.1. Использование менеджера контекста¶
Канонический пример – открытие файла:
with open("data.txt") as f:
text = f.read()
# f is now closed, even if read() failed
open() возвращает файловый объект, который сам является менеджером контекста; __enter__ возвращает файл, а __exit__ закрывает его. Блок with делает правило «всегда закрывать файл по завершении» поведением по умолчанию, а не тем, что вызывающий должен помнить.
2.24.1.1. Несколько менеджеров контекста¶
Один оператор with может войти сразу в несколько менеджеров контекста, разделённых запятыми:
with open("input.txt") as src, open("output.txt", "w") as dst:
dst.write(src.read())
Эквивалентно двум вложенным блокам with, но более плоско. Менеджеры входятся слева направо и выходятся в обратном порядке; если __enter__ у правого менеджера возбуждает исключение, __exit__ левого менеджера всё равно выполнится.
2.24.2. Написание менеджера контекста¶
Любой класс с методами __enter__ и __exit__ работает как менеджер контекста:
class Section:
def __init__(self, label):
self.label = label
def __enter__(self):
print("---", self.label, "---")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("--- end", self.label, "---")
return False
with Section("setup"):
print("doing the work")
Вывод:
--- setup ---
doing the work
--- end setup ---
__exit__ получает три аргумента, описывающих исключение, завершившее блок, или три значения None, если блок завершился нормально. Возврат False (или None) позволяет любому исключению распространиться после завершения; возврат True подавил бы его.
Используйте менеджеры контекста для любого ресурса, имеющего жизненный цикл «открыть / закрыть» или «захватить / освободить» – не только для файлов. Этот шаблон держит очистку в паре с настройкой в той точке, где обе вводятся, поэтому забытый вызов закрытия в середине длинной функции не может привести к утечке ресурса.