Как использовать контекстный менеджер для автоматического закрытия соединения с базой данных в Python?
Pythonавтоматизациябазы данныхконтекстный менеджеруправление ресурсами
Что проверяют
Понимаете ли вы протокол контекстного менеджера, а не только синтаксис with. Следом почти всегда спрашивают, что происходит при исключении внутри блока и закрывает ли соединение with sqlite3.connect(...).
Как отвечать
- протокол:
withвызывает__enter__, результат кладёт в переменную послеas, а при выходе из блока, в том числе по исключению, вызывает__exit__(exc_type, exc, tb). Если__exit__вернёт истинное значение, исключение подавится, поэтому обычно возвращаютNone; - короткий путь:
contextlib.contextmanagerи генератор. Код доyieldоткрывает ресурс, код вfinallyпослеyieldзакрывает. Для асинхронного кода естьasynccontextmanagerиasync with; - готовая обёртка:
contextlib.closing(conn)вызоветclose()у любого объекта; - транзакция и соединение это разные вещи: в
sqlite3иpsycopg2блокwith conn:управляет транзакцией (commit при успехе, rollback при исключении), а соединение оставляет открытым. В psycopg 3with psycopg.connect(...) as conn:на выходе соединение закрывает, это отличие стоит назвать; - в реальном проекте: соединения берут из пула, и менеджер возвращает соединение в пул, а не закрывает его. Так работают
engine.connect()иSessionв SQLAlchemy,pool.connection()в psycopg_pool.
Пример, который закрывает всё сразу:
from contextlib import contextmanager
import sqlite3
@contextmanager
def db(path):
conn = sqlite3.connect(path)
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()Где спотыкаются
Пишут with sqlite3.connect(...) as conn: и считают соединение закрытым. Оно закроется только сборщиком мусора, а в долгоживущем процессе это утечка дескрипторов.
В @contextmanager не оборачивают yield в try/finally. При исключении в блоке код после yield не выполнится, и соединение останется открытым.
Возвращают True из __exit__ на всякий случай и молча глотают все ошибки, включая нарушения целостности данных.