Что такое 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()

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

Оцени свой прогресс

Честно оцени своё понимание этого вопроса, чтобы мы могли построить твой учебный трек максимально эффективно.
Читать в блоге