← Назад к вопросам

Какой протокол у контекстного менеджера?

2.0 Middle🔥 201 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Context Manager Protocol (Протокол контекстного менеджера)

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

Методы протокола

__enter__(self) — вызывается когда выполнение входит в блок with

  • Может возвращать значение (которое присваивается переменной после as)
  • Может выполнять инициализацию ресурсов
  • Возвращаемое значение может быть None или любой объект

__exit__(self, exc_type, exc_val, exc_tb) — вызывается когда выполнение выходит из блока with

  • exc_type — тип исключения (если оно произошло)
  • exc_val — значение исключения
  • exc_tb — traceback исключения
  • Все параметры None если исключения не было
  • Возвращает True если исключение подавлено, False если его нужно распространять
  • Обычно выполняет очистку ресурсов

Простой пример

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        return False  # Не подавляем исключения

# Использование
with FileManager('test.txt', 'w') as f:
    f.write('Hello')
    # При выходе из блока вызовется __exit__ и файл закроется

Пример с обработкой исключений

class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        self.connection = None
    
    def __enter__(self):
        print(f"Подключение к {self.db_name}...")
        self.connection = {"status": "connected"}
        return self.connection
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Отключение от {self.db_name}...")
        self.connection = None
        
        if exc_type is not None:
            print(f"Ошибка: {exc_type.__name__}: {exc_val}")
            return False  # Распространяем исключение
        
        return True

# Использование
try:
    with DatabaseConnection('mydb') as conn:
        print(f"Работаем с {conn}")
        raise ValueError("Что-то пошло не так")
except ValueError as e:
    print(f"Поймали исключение: {e}")

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

Для простых случаев удобнее использовать декоратор из модуля contextlib:

from contextlib import contextmanager

@contextmanager
def my_context():
    print("Вход")
    try:
        yield "ресурс"  # Это вернется в блоке with
    finally:
        print("Выход")

with my_context() as resource:
    print(f"Используем {resource}")

Асинхронные контекстные менеджеры

Для асинхронного кода используются __aenter__ и __aexit__:

class AsyncDatabaseConnection:
    async def __aenter__(self):
        print("Асинхронное подключение...")
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("Асинхронное отключение...")
        return False

# Использование
async def main():
    async with AsyncDatabaseConnection() as conn:
        print("Работаем с асинхронным подключением")

Практические примеры в стандартной библиотеке

# Файлы
with open('file.txt') as f:
    data = f.read()  # __exit__ закроет файл автоматически

# Блокировки (threading)
import threading
lock = threading.Lock()
with lock:  # __enter__ захватит lock, __exit__ отпустит
    # критическая секция
    pass

# Временные объекты
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmpdir:
    # Работаем с временной директорией
    pass  # __exit__ удалит её

Ключевые моменты

  • Гарантированный код очистки: __exit__ вызовется даже если произойдет исключение
  • Управление ресурсами: файлы, соединения, блокировки, память
  • Простота: вместо try-finally пишется читаемый with блок
  • Вложенность: можно использовать несколько контекстных менеджеров одновременно

Протокол контекстного менеджера — это фундаментальная часть Python для правильного управления ресурсами.