Работали ли вы с контекстными менеджерами (context managers) в Python?

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

Ответ

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

Стандартное использование с файлами и сетевыми ресурсами:

# Автоматическое закрытие файла
with open('data.json', 'r') as f:
    data = json.load(f)
# Файл закрыт здесь, даже если внутри блока было исключение

# Работа с сессиями requests
with requests.Session() as session:
    session.auth = ('user', 'pass')
    response = session.get('https://api.example.com/data')
# Сессия корректно закрыта

Создание собственного контекстного менеджера с помощью класса:

import sqlite3
from contextlib import contextmanager

class DatabaseConnection:
    def __init__(self, db_path):
        self.db_path = db_path
        self.conn = None

    def __enter__(self):
        self.conn = sqlite3.connect(self.db_path)
        self.conn.execute('PRAGMA foreign_keys = ON')  # Пример настройки
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.conn.commit()  # Коммит, если не было ошибок
        else:
            self.conn.rollback()  # Откат при ошибке
        self.conn.close()

# Использование
with DatabaseConnection('app.db') as conn:
    cursor = conn.cursor()
    cursor.execute('INSERT INTO users (name) VALUES (?)', ('Alice',))
# Соединение гарантированно закрыто, транзакция завершена

Использование декоратора @contextmanager из contextlib:

from contextlib import contextmanager
import time

@contextmanager
def timer(operation_name):
    """Контекстный менеджер для замера времени выполнения."""
    start = time.perf_counter()
    try:
        yield
    finally:
        end = time.perf_counter()
        print(f"{operation_name} took {end - start:.4f} seconds")

with timer("Data processing"):
    # Долгая операция
    process_large_dataset()

В реальных проектах я применял контекстные менеджеры для управления пулами соединений с БД, временной смены текущей директории (contextlib.chdir), а также для реализации механизма блокировок (lock) при работе с многопоточным кодом.