← Назад к вопросам
Какие магические методы нужно переопределить для создания контекстного менеджера?
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
Основные правила
- enter возвращает объект, который присваивается переменной после
as - exit всегда вызывается, даже если было исключение
- Если exit возвращает True, исключение подавляется
- Если exit возвращает False или None, исключение распространяется
- Используй @contextmanager для простых случаев
- Используй класс с enter/exit для сложной логики
Контекстные менеджеры — это мощный инструмент для управления ресурсами и обеспечения того, что cleanup код всегда выполнится, даже при ошибках.