Как ответить
Контекстный менеджер в Python — это объект, который определяет методы __enter__ и __exit__, и используется с конструкцией with. Его главная задача — гарантировать выполнение кода при входе в блок и при выходе из него, даже если внутри произошла ошибка. Самый частый пример — работа с файлами: открыл, прочитал, закрыл. Без контекстного менеджера легко забыть вызвать close(), особенно если код упал. С with это происходит автоматически.
Вот как это выглядит на практике. Без контекстного менеджера:
f = open('file.txt', 'r')
try:
data = f.read()
finally:
f.close()С контекстным менеджером:
with open('file.txt', 'r') as f:
data = f.read()Код короче, и блок finally не нужен — __exit__ вызовется в любом случае. Встроенные менеджеры есть не только для файлов: threading.Lock для блокировок, subprocess.Popen для процессов, contextlib.redirect_stdout для перенаправления вывода. Можно написать и свой. Допустим, нужно замерить время выполнения функции:
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.time() - self.start
print(f'Elapsed: {self.elapsed:.2f}s')
with Timer() as t:
# какой-то код
time.sleep(1)
print(t.elapsed) # 1.00Обрати внимание на сигнатуру __exit__: три параметра — тип исключения, значение и traceback. Если внутри блока произошла ошибка, они передаются в этот метод. Если метод возвращает True, исключение подавляется. Если False или None — пробрасывается дальше. Это удобно, например, для логирования ошибок без остановки выполнения.
Ещё есть contextlib.contextmanager — декоратор, который превращает генератор в контекстный менеджер. Тот же таймер можно написать так:
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
try:
yield
finally:
print(f'Elapsed: {time.time() - start:.2f}s')
with timer():
time.sleep(1)Тут важно: yield разделяет код до и после блока. Всё, что до yield, — это __enter__, всё, что после, — __exit__. Оборачивать в try/finally нужно, чтобы гарантировать выполнение кода даже при исключении.
На собеседовании я бы ещё упомянул, что контекстные менеджеры часто используют для управления соединениями с БД (открыть/закрыть), транзакциями (commit/rollback) или временными файлами. Это стандартный паттерн для ресурсов, которые нужно освобождать.