Что такое контекстный менеджер в Python?

«Что такое контекстный менеджер в Python?» — вопрос из категории Python, который задают на 33% собеседований Data Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Контекстный менеджер в Python — это объект, который определяет контекст выполнения для блока кода с помощью методов __enter__() и __exit__(). Он гарантирует, что определенные операции (например, выделение и освобождение ресурсов) будут выполнены до и после этого блока, даже если внутри возникло исключение. Используется с оператором with.

Зачем это нужно? Для безопасного управления ресурсами (файлы, сетевые соединения, транзакции БД, блокировки), чтобы избежать утечек и обеспечить корректное завершение.

Пример использования встроенного контекстного менеджера для работы с файлами:

# Без контекстного менеджера (потенциально небезопасно)
f = open('file.txt', 'r')
try:
    data = f.read()
finally:
    f.close()  # Необходимо не забыть закрыть

# С контекстным менеджером (рекомендуемый способ)
with open('file.txt', 'r') as f:
    data = f.read()
# Файл автоматически закрыт здесь, даже если при чтении возникла ошибка

Создание собственного контекстного менеджера:

  1. Как класс:

    class Timer:
        def __init__(self, name):
            self.name = name
        def __enter__(self):
            self.start = time.time()
            return self  # Объект, который будет связан с переменной после 'as'
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.end = time.time()
            print(f'{self.name}: {self.end - self.start:.2f} сек')
    
    with Timer('Мой код') as t:
        time.sleep(1)
    # Вывод: Мой код: 1.00 сек
  2. С использованием contextlib.contextmanager (как генератор):

    from contextlib import contextmanager
    import time
    
    @contextmanager
    def timer(name):
        start = time.time()
        yield  # Выполнение переходит в блок 'with'
        end = time.time()
        print(f'{name}: {end - start:.2f} сек')
    
    with timer('Замер'):
        time.sleep(0.5)