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

Какие магические методы нужно переопределить для создания контекстного менеджера?

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

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

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

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

Магические методы для контекстного менеджера

Контекстные менеджеры в Python обеспечивают безопасное управление ресурсами. За 10+ лет использую их постоянно для работы с файлами, подключениями БД и транзакциями.

enter и exit

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

class MyContextManager:
    def __enter__(self):
        """Вызывается при входе в блок with"""
        print("Входим в контекст")
        # Здесь инициализируем ресурсы
        self.resource = "initialized"
        return self  # Это значение присваивается переменной после 'as'
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Вызывается при выходе из блока with"""
        print("Выходим из контекста")
        # Здесь очищаем ресурсы
        if exc_type:
            print(f"Произошла ошибка: {exc_type.__name__}")
        # Возвращаем True если исключение обработано, False иначе
        return False

# Использование
with MyContextManager() as cm:
    print(cm.resource)  # "initialized"
    # Выполняются __enter__ и при выходе __exit__

Практический пример: управление файлом

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        print(f"Открываем файл {self.filename}")
        self.file = open(self.filename, self.mode)
        return self.file
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Закрываем файл {self.filename}")
        if self.file:
            self.file.close()
        if exc_type:
            print(f"Ошибка при чтении файла: {exc_val}")
        return False  # Не подавляем исключение

# Использование
with FileManager("data.txt", "r") as file:
    content = file.read()
    print(content)
# Файл автоматически закрывается

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

class SafeConnection:
    def __init__(self, host):
        self.host = host
        self.connection = None
    
    def __enter__(self):
        print(f"Подключаемся к {self.host}")
        self.connection = f"Connection to {self.host}"
        return self.connection
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Закрываем подключение к {self.host}")
        
        # exc_type — тип исключения
        # exc_val — значение исключения
        # exc_tb — traceback
        
        if exc_type is not None:
            print(f"Ошибка: {exc_type.__name__}: {exc_val}")
            # Возвращаем True если хотим подавить исключение
            return False  # Не подавляем, позволяем распространяться
        return False

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

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

class Transaction:
    def __init__(self, db):
        self.db = db
        self.is_committed = False
    
    def __enter__(self):
        print("Начинаем транзакцию")
        self.db.begin()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            print("Коммитим транзакцию")
            self.db.commit()
            self.is_committed = True
        else:
            print(f"Откатываем транзакцию из-за {exc_type.__name__}")
            self.db.rollback()
            # Подавляем исключение и не даём ему распространяться
            return True  # True = исключение обработано

db = MockDB()

with Transaction(db) as trans:
    print("Выполняем операции")
    # Даже если ошибка, транзакция откатится и исключение не распространится

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

Легче всего использовать декоратор из модуля contextlib:

from contextlib import contextmanager

@contextmanager
def managed_resource(name):
    print(f"Выделяем ресурс {name}")
    resource = f"Resource-{name}"
    try:
        yield resource  # Это значение присваивается переменной после 'as'
    except Exception as e:
        print(f"Ошибка: {e}")
        raise
    finally:
        print(f"Освобождаем ресурс {name}")

# Использование
with managed_resource("my-resource") as res:
    print(f"Работаем с {res}")

Реальный пример: контекстный менеджер для БД

class DatabaseConnection:
    def __init__(self, connection_string):
        self.connection_string = connection_string
        self.conn = None
    
    def __enter__(self):
        print(f"Подключаемся к БД: {self.connection_string}")
        # Имитируем подключение
        self.conn = {"status": "connected"}
        return self.conn
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Закрываем подключение к БД")
        if self.conn:
            self.conn["status"] = "closed"
        
        if exc_type:
            print(f"Ошибка БД: {exc_val}")
            return False  # Не подавляем исключение
        return False

# Использование
with DatabaseConnection("postgresql://localhost/mydb") as db:
    # Выполняем запросы
    print(f"Статус БД: {db['status']}")

Параметры exit

def __exit__(self, exc_type, exc_val, exc_tb):
    # exc_type  — класс исключения (None если ошибки не было)
    # exc_val   — экземпляр исключения (None если ошибки не было)
    # exc_tb    — traceback объект (None если ошибки не было)
    
    if exc_type is None:
        # Код выполнен успешно
        pass
    elif exc_type is ValueError:
        # Конкретное исключение
        pass
    else:
        # Другое исключение
        pass
    
    # Возвращаемое значение:
    # True — исключение подавляется
    # False или None — исключение распространяется
    return False

Основные правила

  1. enter возвращает объект, который присваивается переменной после as
  2. exit всегда вызывается, даже если было исключение
  3. Если exit возвращает True, исключение подавляется
  4. Если exit возвращает False или None, исключение распространяется
  5. Используй @contextmanager для простых случаев
  6. Используй класс с enter/exit для сложной логики

Контекстные менеджеры — это мощный инструмент для управления ресурсами и обеспечения того, что cleanup код всегда выполнится, даже при ошибках.