with:
import sqlite3
class DatabaseConnection:
def __init__(self, db_name):
self.db_name = db_name
def __enter__(self):
self.conn = sqlite3.connect(self.db_name)
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
if self.conn:
self.conn.close()
with DatabaseConnection("mydatabase.db") as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
print(cursor.fetchall())
В __enter__ устанавливается соединение и возвращается объект соединения. __exit__ гарантирует закрытие соединения (conn.close()) при выходе из блока with, независимо от ошибок. Аргументы exc_type, exc_val, exc_tb позволяют обрабатывать исключения.
Контекстные менеджеры в Python предоставляют элегантный способ управления ресурсами, такими как подключения к базам данных, гарантируя их корректное освобождение (закрытие) даже в случае возникновения исключений. Это делается с помощью оператора with, который автоматически вызывает методы __enter__() при входе в блок и __exit__() при выходе, независимо от того, было ли исключение или нет.
Основная идея:
__enter__() и __exit__().__enter__() выполняет действия по установлению соединения с базой данных и возвращает объект соединения.__exit__() выполняет действия по закрытию соединения, независимо от того, произошло ли исключение. Метод также получает информацию об исключении (тип, значение, traceback), если оно было.with использует этот класс для автоматического управления соединением.Пример с использованием библиотеки `sqlite3`:
import sqlite3
class DatabaseConnection:
def __init__(self, db_name):
self.db_name = db_name
self.connection = None # Инициализируем connection в None
def __enter__(self):
self.connection = sqlite3.connect(self.db_name)
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
if self.connection:
if exc_type is None: # Если исключений не было, коммитим
self.connection.commit()
else:
self.connection.rollback() # Откатываем изменения при исключении
self.connection.close()
# Обработка исключений (опционально). Если вернуть True, исключение подавляется.
return False # Не подавляем исключение, позволяем ему подняться дальше
# Использование контекстного менеджера:
try:
with DatabaseConnection("mydatabase.db") as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
cursor.execute("INSERT INTO users (name) VALUES (?)", ("Alice",))
cursor.execute("INSERT INTO users (name) VALUES (?)", ("Bob",))
# Пример с ошибкой, чтобы увидеть rollback
# cursor.execute("INSERT INTO users (name) VALUES (?)", (123,)) # Вызовет TypeError
except sqlite3.Error as e:
print(f"Произошла ошибка базы данных: {e}")
# Подключение автоматически закрывается здесь, даже если было исключение.
# Пример запроса вне контекстного менеджера, показывающий, что БД закрыта
try:
conn = sqlite3.connect("mydatabase.db") # Открываем новое подключение
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
rows = cursor.fetchall()
print("Данные из базы данных (вне контекста):", rows)
conn.close()
except sqlite3.Error as e:
print(f"Ошибка при попытке подключения вне контекста: {e}")
Объяснение:
DatabaseConnection инкапсулирует логику подключения и закрытия базы данных.__enter__() устанавливает соединение с базой данных, используя sqlite3.connect() и возвращает объект соединения.__exit__() закрывает соединение, гарантируя, что оно будет закрыто независимо от того, возникло ли исключение. Важно отметить, что здесь проверяется наличие исключения. Если исключения не было (exc_type is None), то изменения коммитятся. В противном случае, выполняется откат изменений (conn.rollback()), чтобы транзакция была атомарной.with DatabaseConnection("mydatabase.db") as conn: создает экземпляр класса DatabaseConnection и вызывает __enter__(), результат которого присваивается переменной conn. После завершения блока with автоматически вызывается __exit__().with вы можете безопасно использовать соединение conn для выполнения операций с базой данных.Преимущества использования контекстных менеджеров:
Важно: Важно правильно обрабатывать исключения внутри __exit__(), чтобы гарантировать, что ресурс будет освобожден корректно, даже если произошла ошибка. Возвращаемое значение __exit__ определяет, будет ли исключение подавлено (True) или проброшено дальше (False, по умолчанию). В большинстве случаев, исключение лучше не подавлять, а позволить ему подняться дальше, чтобы приложение могло корректно обработать ошибку.
Этот подход легко адаптируется для других библиотек баз данных, таких как `psycopg2` для PostgreSQL или `pymysql` для MySQL, заменив только код подключения и закрытия.