6. Что такое контекстный менеджер в Python?
6. Что такое контекстный менеджер в Python?.md
Контекстные менеджеры в Python
Контекстный менеджер — объект, который управляет входом и выходом из блока with, гарантируя выполнение инициализации и очистки ресурсов независимо от того, возникло исключение или нет.
Реализуется через методы __enter__ / __exit__ или декоратор @contextmanager.
Классическая реализация
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_value, traceback):
if self.file:
self.file.close()
with FileManager("test.txt", "w") as f:
f.write("Hello, world!")
Через contextlib (чаще на практике)
from contextlib import contextmanager
@contextmanager
def open_file(name, mode):
f = open(name, mode)
try:
yield f
finally:
f.close()
with open_file("test.txt", "w") as f:
f.write("Hello")
Когда применяется
Везде, где нужно гарантированно освободить ресурс или откатить состояние:
- Файлы (
open) - Подключения к БД (commit / rollback)
- Сетевые соединения
- Локи (
threading.Lock) - Транзакции
Преимущества: код безопаснее, читаемость выше, исключения обрабатываются корректно.
В Django
Транзакции
from django.db import transaction
# Автоматический rollback при исключении
with transaction.atomic():
order = Order.objects.create(user=user, total=500)
Payment.objects.create(order=order, amount=500)
# Если здесь упадёт — оба объекта не сохранятся
Отключение сигналов (например, при импорте данных)
from contextlib import contextmanager
from django.db.models.signals import post_save
@contextmanager
def mute_signals(*signals):
handlers = {}
for signal in signals:
handlers[signal] = signal.receivers
signal.receivers = []
try:
yield
finally:
for signal, receivers in handlers.items():
signal.receivers = receivers
with mute_signals(post_save):
bulk_import_users(data) # без лишних сигналов
Переключение базы данных
from django.test.utils import override_settings
with override_settings(DATABASES=test_db_config):
run_migration_test()
В FastAPI
Lifespan — инициализация при старте приложения
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: подключаемся к БД, кешу и т.д.
await database.connect()
await redis.initialize()
yield
# Shutdown: освобождаем ресурсы
await database.disconnect()
await redis.close()
app = FastAPI(lifespan=lifespan)
Dependency Injection с очисткой (через yield)
from sqlalchemy.orm import Session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
return db.query(User).filter(User.id == user_id).first()
FastAPI автоматически вызывает всё после
yieldпосле завершения запроса — даже если он упал с ошибкой.