Что такое with (контекстный менеджер)?

Краткий ответ (для собеседования): with — это конструкция, которая работает с контекстными менеджерами и берёт на себя корректное «открытие» и «закрытие» ресурсов (файлы, соединения с БД, блокировки и т.п.), даже если внутри блока происходит ошибка. Контекстный менеджер — это объект с методами __enter__ и __exit__, которые автоматически вызываются при входе в блок with и при выходе из него.

Подробный ответ

Развернутое объяснение

Контекстный менеджер — это объект, который умеет настраивать некоторый контекст перед выполнением блока кода и гарантированно его «убирать» после, независимо от того, завершился блок успешно или с исключением. Конструкция with как раз и используется для работы с такими объектами:

with open("data.txt") as f:
   contents = f.read()
# здесь файл уже закрыт

В этом примере при входе в блок with у объекта файла вызывается __enter__, возвращаемое значение попадает в переменную f;

при выходе (даже если read() бросит исключение) будет вызван __exit__, который закроет файл. with — это «синтаксический сахар» над типичной конструкцией try/finally, упрощающий правильную работу с ресурсами.

Как это выглядит внутри? Любой объект можно сделать контекстным менеджером, реализовав два метода:

class MyContext:
   def __enter__(self):
       # код при входе в контекст (настройка ресурса)
       return self  # то, что попадёт в переменную после as
   def __exit__(self, exc_type, exc_val, exc_tb):
       # код при выходе (освобождение ресурса, логирование и т.п.)
       # вернуть True, чтобы «поглотить» исключение, или False/None, чтобы пробросить
       ...

Использование:

with MyContext() as ctx:
   # код внутри контекста
   ...

На собеседовании обычно достаточно сказать, что контекстный менеджер — это объект с методами __enter__ и __exit__, а with гарантирует вызов __exit__ при выходе из блока.

Практические примеры использования with

Типичные сценарии использования контекстного менеджера, о которых полезно упомянуть:

Работа с файлами:

with open("log.txt", "a") as log_file:
   log_file.write("New event\n")
# файл закрыт автоматически

Блокировки и потоки:

from threading import Lock
lock = Lock()
with lock:
   # критическая секция
   ...

Временное изменение окружения, таймеры и т.п.

import time
class Timer:
   def __enter__(self):
       self.start = time.time()
       return self
   def __exit__(self, exc_type, exc_val, exc_tb):
       self.end = time.time()
       print(f"Elapsed: {self.end - self.start:.3f}s")
with Timer():
   do_something()

Такие примеры часто спрашивают на интервью, чтобы проверить, понимаете ли вы идею «автоматической уборки».

Читать полную статью в блоге