5.02. Работа с данными и внешним миром
Работа с данными и внешним миром
Работа с файлами и системой
Python предоставляет широкий спектр средств для взаимодействия с файловой системой и внешними ресурсами. Эти возможности реализованы как в стандартной библиотеке, так и через специализированные модули.
В Python файлы обрабатываются как объекты потока (file objects), предоставляющие интерфейс для последовательного доступа к данным. Основная функция для открытия файлов — open(). Она возвращает объект файла, через который выполняются операции ввода-вывода.
Синтаксис:
file = open(path, mode='r', encoding=None)
Параметры:
- path — строка или объект пути, указывающий на файл.
- mode — режим открытия файла (подробно ниже).
- encoding — кодировка текста (актуально для текстовых режимов).
Прямое использование open() требует явного вызова метода .close(), что чревато утечками ресурсов при ошибках. Для гарантированного освобождения ресурсов применяется контекстный менеджер with.
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
# Файл закрыт автоматически после выхода из блока
Контекстный менеджер обеспечивает корректное завершение операций даже в случае исключений, вызывая метод __exit__ у объекта файла, который, в свою очередь, вызывает .close().
Использование with считается стандартной практикой при работе с файлами. Прямые вызовы open() без close() допустимы только в специализированных случаях (например, при передаче дескриптора между функциями).
Режим открытия файлов определяет тип доступа к файлу и способ интерпретации данных.
'r'- Только чтение (по умолчанию). Файл должен существовать.'w'- Запись. Создаёт новый файл или перезаписывает существующий. Он также полностью очищает содержимое файла при открытии. Это поведение необходимо учитывать при непреднамеренной перезаписи.'a'- Добавление. Открывает файл для записи в конец. Если файл не существует — создаётся. Он также всегда перемещает указатель записи в конец файла, даже если вы попытаетесь изменить позицию с помощью seek().'x'- Эксклюзивное создание. Открывает файл только если он ещё не существует. Генерирует FileExistsError, если файл уже есть.'b'- Бинарный режим. Используется совместно с другими режимами (rb, wb). Данные передаются как bytes.'t'- Текстовый режим (по умолчанию). Данные интерпретируются как строки (str) с учётом кодировки.'+'- Обновление. Позволяет одновременно читать и писать (r+, w+, a+ ).
Бинарные режимы ('rb', 'wb') не выполняют преобразования окончаний строк (\n → \r\n) и не используют кодировку. Они необходимы при работе с изображениями, архивами, сериализованными объектами.
Чтение текстового файла:
with open('example.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
Запись в бинарный файл:
data = b'\x00\xFF\xA5'
with open('binary.dat', 'wb') as f:
f.write(data)
Добавление строки в лог:
with open('app.log', 'a', encoding='utf-8') as f:
f.write("Ошибка подключения\n")
Файловые пути — центральный элемент взаимодействия с файловой системой.
Python предлагает два основных подхода: традиционный модуль os.path и современный объектно-ориентированный pathlib.
Модуль os.path - часть модуля os, предоставляющая функции для манипуляции путями в виде строк.
import os
path = '/home/user/docs/file.txt'
print(os.path.basename(path)) # 'file.txt'
print(os.path.dirname(path)) # '/home/user/docs'
print(os.path.splitext(path)) # ('/home/user/docs/file', '.txt')
print(os.path.exists(path)) # True/False
print(os.path.isfile(path)) # Проверка типа
print(os.path.join('folder', 'sub', 'file.txt')) # Сборка пути с учётом ОС
Особенность: все функции работают со строками. Это может привести к ошибкам при некорректной обработке разделителей (\ vs /).
Модуль pathlib появился в Python 3.4 как более современная альтернатива. Предоставляет объектно-ориентированный интерфейс для путей.
from pathlib import Path
p = Path('/home/user/docs/file.txt')
print(p.name) # 'file.txt'
print(p.suffix) # '.txt'
print(p.stem) # 'file'
print(p.parent) # PosixPath('/home/user/docs')
print(p.exists()) # True/False
print(p.is_file()) # Проверка типа
# Сборка путей
new_path = p.parent / 'backup' / p.name
print(new_path) # /home/user/docs/backup/file.txt
# Поиск файлов по шаблону
for pyfile in Path('.').glob('*.py'):
print(pyfile)
pathlib является предпочтительным выбором в новых проектах благодаря читаемости кода, поддержке оператора / для объединения путей, и наличию методов для итерации, фильтрации и рекурсивного обхода. Он интегрирован с большинством функций стандартной библиотеки, принимающих строки путей.
Для массовых операций над файлами используются комбинация обхода файловой системы и системных вызовов.
Обход каталогов через os.listdir() и os.walk():
import os
# Простой список файлов в каталоге
for filename in os.listdir('/path/to/dir'):
print(filename)
# Рекурсивный обход
for root, dirs, files in os.walk('/path/to/root'):
for file in files:
filepath = os.path.join(root, file)
print(filepath)
Через pathlib:
from pathlib import Path
p = Path('/path/to/dir')
for file in p.rglob('*'): # Рекурсивно
if file.is_file():
print(file)
Переименование файлов через метод os.rename() или Path.rename():
import os
os.rename('old_name.txt', 'new_name.txt')
# Или через pathlib
Path('old.txt').rename('new.txt')
rename() может перемещать файлы между каталогами, но не гарантирует работу между разными файловыми системами.
Копирование и удаление использует модуль shutil. Но базовые операции есть и в os:
import os
# Удаление файла
os.remove('file.txt') # или os.unlink()
# Удаление пустого каталога
os.rmdir('empty_dir/')
# Удаление непустого каталога — невозможно через os
Модуль shutil предоставляет удобные функции для копирования, перемещения и удаления файлов и деревьев каталогов.
Копирование:
import shutil
# Копирование файла
shutil.copy('source.txt', 'dest.txt') # Сохраняет метаданные (время модификации)
shutil.copy2('source.txt', 'dest.txt') # Сохраняет больше метаданных (включая время доступа)
# Копирование дерева каталогов
shutil.copytree('src_folder/', 'backup/')
copytree() создаёт целевую директорию и рекурсивно копирует содержимое. При наличии целевого каталога — ошибка. Можно использовать dirs_exist_ok=True (начиная с Python 3.8).
Перемещение:
shutil.move('old_location/', 'new_location/')
Это аналог mv в Unix. Может перемещать как файлы, так и каталоги. Если целевой путь находится на другой файловой системе — выполняется копирование с последующим удалением источника.
Удаление дерева каталогов:
shutil.rmtree('unwanted_dir/')
Удаляет каталог и всё его содержимое. Аналог rm -rf. Будьте осторожны: операция необратима.
Если же речь идёт о создании структуры проекта, то автоматизация создания директорий — частая задача при инициализации проектов.
Создание каталогов:
import os
from pathlib import Path
# Через os
os.makedirs('project/src/utils', exist_ok=True) # exist_ok избегает ошибки, если путь уже есть
# Через pathlib
Path('project/data/raw').mkdir(parents=True, exist_ok=True)
Оба метода поддерживают parents=True (создание промежуточных каталогов) и exist_ok=True (игнорирование ошибки существования).
Пример - скрипт инициализации проекта:
def create_project_structure(base_path):
structure = [
'src',
'src/utils',
'tests',
'docs',
'data/raw',
'data/processed',
'notebooks'
]
base = Path(base_path)
for folder in structure:
(base / folder).mkdir(parents=True, exist_ok=True)
# Создание файлов-заглушек
(base / 'src' / '__init__.py').touch()
(base / 'README.md').write_text('# Проект\nОписание\n')
create_project_structure('./my_project')
Python часто используется для написания скриптов автоматической очистки временных файлов, логов, кэшей. Пример: очистка старых логов:
from pathlib import Path
import time
def cleanup_logs(log_dir, days=30):
log_path = Path(log_dir)
cutoff = time.time() - (days * 86400) # 86400 секунд в сутках
for logfile in log_path.glob('*.log'):
if logfile.stat().st_mtime < cutoff:
print(f"Удаление {logfile}")
logfile.unlink()
cleanup_logs('/var/log/myapp', days=7)
Такие скрипты могут быть запланированы через cron (Linux) или Task Scheduler (Windows).
Python предоставляет полный набор инструментов для работы с файловой системой на уровне, достаточном для создания сложных системных утилит, скриптов развёртывания, обработки данных и управления проектами. Современные практики рекомендуют использовать pathlib для работы с путями, with для управления контекстом файлов и shutil для сложных операций с каталогами.
Выбор между os и pathlib зависит от контекста: os остаётся актуальным в легаси-коде и при необходимости низкоуровневого контроля, тогда как pathlib предпочтителен в новых проектах за счёт своей ясности и безопасности.
Все операции с файловой системой должны выполняться с учётом особенностей платформы (разделители путей, права доступа, кодировки), а также с должным уровнем обработки исключений (try...except), особенно при автоматизации.
Форматы данных: JSON, CSV, XML
JSON — текстовый формат представления структурированных данных на основе пар «ключ-значение» и упорядоченных списков. Поддерживает типы: строки, числа, логические значения, null, объекты и массивы.
Python предоставляет встроенную поддержку через модуль json. Основные функции:
json.dumps(obj)- Сериализует объект Python в строку JSON;json.loads(s)- Десериализует строку JSON в объект Python;json.dump(obj, file)- Записывает сериализованный объект в файловый объект;json.load(file)- Читает и десериализует JSON из файлового объекта.
Сериализация и десериализация:
import json
data = {
"users": [
{"id": 1, "name": "Alice", "active": True},
{"id": 2, "name": "Bob", "active": False}
],
"total": 2
}
# Сериализация в строку
json_str = json.dumps(data, indent=2)
print(json_str)
# Сохранение в файл
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# Чтение из файла
with open('data.json', 'r', encoding='utf-8') as f:
loaded_data = json.load(f)
Думаю, нужно пояснить параметры:
ensure_ascii=False— позволяет сохранять кириллицу и другие символы без экранирования.indent=2— форматирует вывод с отступами для читаемости.
Также стоит отметить и валидацию данных при загрузке.
Модуль json не выполняет семантической валидации. Он проверяет только синтаксис. При ошибке парсинга выбрасывается исключение json.JSONDecodeError.
try:
data = json.loads(invalid_json_string)
except json.JSONDecodeError as e:
print(f"Ошибка парсинга JSON: {e}")
Для полноценной валидации структуры данных (например, обязательных полей, типов) следует использовать сторонние библиотеки (jsonschema) или ручные проверки.
Пример обработки datetime:
from datetime import datetime
import json
data = {"timestamp": datetime.now()}
def serialize(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Тип {type(obj)} не сериализуем в JSON")
json_str = json.dumps(data, default=serialize)
CSV — простой формат таблиц данных, где каждая строка представляет запись, а поля разделяются разделителем (чаще всего запятой).
Поддерживает различные диалекты (разделители, кавычки, экранирование).
Модуль csv предоставляет гибкий интерфейс для чтения и записи CSV-файлов.
Чтение CSV:
import csv
with open('users.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
header = next(reader) # Заголовок
for row in reader:
print(row) # row — список значений
Если файл содержит заголовок, удобнее использовать DictReader:
with open('users.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(row['name'], row['age']) # Доступ по ключу
Запись CSV:
headers = ['name', 'age', 'city']
data = [
['Alice', 30, 'Moscow'],
['Bob', 25, 'Saint Petersburg']
]
with open('output.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
writer.writerow(headers)
writer.writerows(data)
Параметр newline='' обязателен при записи в Windows, чтобы избежать лишних пустых строк.
Использование DictWriter:
with open('output.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=headers)
writer.writeheader()
writer.writerow({'name': 'Alice', 'age': 30, 'city': 'Moscow'})
Можно задавать разделитель, кавычки, способ экранирования:
csv.reader(f, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
XML — иерархический формат разметки, основанный на тегах. Поддерживает атрибуты, вложенные элементы, пространства имён. Используется в конфигурациях, веб-сервисах (SOAP), документах.
Модуль xml.etree.ElementTree (часто импортируется как ET) — стандартный инструмент для работы с XML.
Чтение и разбор XML:
import xml.etree.ElementTree as ET
tree = ET.parse('users.xml')
root = tree.getroot()
for user in root.findall('user'):
user_id = user.get('id') # Атрибут
name = user.find('name').text
age = user.find('age').text
print(f"ID: {user_id}, Name: {name}, Age: {age}")
Создание XML:
root = ET.Element('users')
user1 = ET.SubElement(root, 'user', {'id': '1'})
ET.SubElement(user1, 'name').text = 'Alice'
ET.SubElement(user1, 'age').text = '30'
tree = ET.ElementTree(root)
tree.write('new_users.xml', encoding='utf-8', xml_declaration=True)
Особенности:
- find() — возвращает первый совпадающий элемент.
- findall() — все совпадения.
- get(attr) — значение атрибута.
- text — содержимое элемента.
- Поддержка XPath-подобных выражений (ограниченных):
root.find('user[@id="1"]').
Рассмотрим задачу конвертации CSV-файла в JSON с сохранением типов данных.
Исходный CSV (input.csv):
name,age,active
Alice,30,true
Bob,25,false
Реализация
import csv
import json
def csv_to_json(csv_path, json_path):
data = []
with open(csv_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
# Преобразование типов
row['age'] = int(row['age'])
row['active'] = row['active'].lower() == 'true'
data.append(row)
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# Вызов
csv_to_json('input.csv', 'output.json')
Результат (output.json):
[
{
"name": "Alice",
"age": 30,
"active": true
},
{
"name": "Bob",
"age": 25,
"active": false
}
]
Парсинг и веб-скрапинг
В условиях роста объёмов публичных данных в интернете, извлечение информации с веб-сайтов — распространённая задача для анализа рынка, мониторинга цен, сбора новостей и обучения моделей. Python предоставляет мощные инструменты для автоматизированного получения и обработки веб-контента. В данной подглаве рассматриваются основные подходы к веб-скрапингу, ключевые библиотеки, типичные паттерны использования и ограничения.
Для получения HTML-страниц (или любого HTTP-ресурса) используется библиотека requests — де-факто стандарт для HTTP-взаимодействия в Python.
Примечание: requests не является частью стандартной библиотеки. Требует установки:
pip install requests
Основные операции:
import requests
# GET-запрос
response = requests.get('https://httpbin.org/html')
# Проверка статуса
if response.status_code == 200:
html = response.text # Текст ответа (в кодировке, указанной в headers)
else:
print(f"Ошибка: {response.status_code}")
Для корректного взаимодействия с серверами рекомендуется настраивать заголовки, параметры и таймауты.
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
}
params = {'q': 'laptop', 'page': 1}
try:
response = requests.get(
'https://example-shop.com/search',
params=params,
headers=headers,
timeout=10 # Секунды
)
response.raise_for_status() # Выбрасывает исключение при 4xx/5xx
except requests.RequestException as e:
print(f"Ошибка запроса: {e}")
Использование User-Agent помогает избежать блокировок, так как многие сайты фильтруют запросы от «неизвестных» клиентов. timeout предотвращает зависание скрипта при недоступности сервера. А raise_for_status() позволяет централизованно обрабатывать HTTP-ошибки.
Иногда сервер не указывает правильную кодировку. В этом случае можно принудительно задать её:
response.encoding = 'utf-8' # или 'cp1251' для старых русскоязычных сайтов
html = response.text
Полученный HTML требует структурированной обработки. Для этого используется BeautifulSoup — парсер разметки, позволяющий удобно извлекать данные по тегам, классам, атрибутам.
pip install beautifulsoup4
Основы использования:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'html.parser') # 'lxml' или 'html5lib' — альтернативы
Поддерживаются различные парсеры:
- html.parser — встроенный, не требует зависимостей.
- lxml — быстрее, но требует установки lxml.
- html5lib — наиболее корректно обрабатывает «грязный» HTML.
Поиск элементов:
# По тегу
title = soup.find('h1')
all_links = soup.find_all('a')
# По классу CSS
price = soup.find('div', class_='price')
# По атрибутам
img = soup.find('img', src=True)
# По нескольким условиям
product = soup.find('div', {'class': 'product', 'data-id': '123'})
Навигация по дереву:
# Дочерние элементы
for child in soup.div.children:
print(child.name)
# Родитель
parent = price.parent
# Соседи
next_elem = price.next_sibling
Извлечение данных:
text = element.get_text(strip=True) # Удаление лишних пробелов
href = link['href'] # Атрибут (выбрасывает KeyError, если нет)
src = img.get('src') # Безопасное получение (вернёт None)
Рассмотрим гипотетический сценарий — извлечение названий и цен товаров со страницы каталога. Цель - спарсить все товары с URL https://shop.example.com/category/laptops, получить:
- название
(<h3 class="title">) - цену
(<span class="price">) - ссылку
(<a href="...">)
Реализация:
import requests
from bs4 import BeautifulSoup
import csv
def scrape_prices(url):
headers = {'User-Agent': 'Data Scraper 1.0'}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
response.encoding = 'utf-8'
except requests.RequestException as e:
print(f"Не удалось загрузить страницу: {e}")
return []
soup = BeautifulSoup(response.text, 'html.parser')
products = []
for item in soup.find_all('div', class_='product-item'):
title_elem = item.find('h3', class_='title')
price_elem = item.find('span', class_='price')
link_elem = item.find('a', href=True)
if not all([title_elem, price_elem, link_elem]):
continue # Пропускаем повреждённые карточки
title = title_elem.get_text(strip=True)
price_text = price_elem.get_text(strip=True).replace('₽', '').replace(' ', '')
try:
price = float(price_text)
except ValueError:
price = None
link = link_elem['href']
if not link.startswith('http'):
link = 'https://shop.example.com' + link # Формируем полный URL
products.append({
'title': title,
'price': price,
'url': link
})
return products
# Запуск
data = scrape_prices('https://shop.example.com/category/laptops')
# Сохранение в CSV
with open('prices.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=['title', 'price', 'url'])
writer.writeheader()
writer.writerows(data)
Файл robots.txt определяет правила доступа для скраперов. Перед началом сбора данных его следует проверить:
import urllib.robotparser
rp = urllib.robotparser.RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()
can_fetch = rp.can_fetch("MyBot", "/category/laptops")
print(can_fetch) # True/False
Даже если robots.txt разрешает доступ, это не означает, что скрапинг допустим юридически.
Чрезмерно частые запросы могут перегружать сервер. Рекомендуется добавлять задержки:
import time
for url in urls:
data = scrape(url)
time.sleep(1.5) # Пауза между запросами
Юридические и этические аспекты? Да, они есть. Нельзя парсить и скрапить всё подряд. Условия использования сайта могут запрещать автоматизированный сбор данных. Некоторые данные защищены авторским правом (например, тексты, цены). Обход CAPTCHA или авторизации может считаться нарушением закона (например, CFAA в США). Персональные данные (ФИО, email, телефоны) регулируются законами (GDPR, CCPA).
Для динамических страниц можно использовать Selenium.
Многие современные сайты используют JavaScript для загрузки контента. В таких случаях статический HTML, возвращаемый requests, не содержит нужных данных.
Когда использовать Selenium?
- Контент подгружается через AJAX.
- Необходимо взаимодействие: клики, прокрутка, форма входа.
- Страница требует выполнения JS для отображения данных.
Это сторонняя библиотека, так что требуется установка:
pip install selenium
Требуется также драйвер браузера (например, chromedriver).
Пример: загрузка динамического контента:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = webdriver.ChromeOptions()
options.add_argument('--headless') # Фоновый режим
driver = webdriver.Chrome(options=options)
try:
driver.get('https://dynamic-shop.com/products')
# Ожидание появления элементов
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'product-item')))
# Прокрутка до конца (если есть lazy loading)
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
# Парсинг через BeautifulSoup
soup = BeautifulSoup(driver.page_source, 'html.parser')
prices = [p.get_text() for p in soup.find_all('span', class_='price')]
finally:
driver.quit()
Работа с базами данных
Взаимодействие Python с базами данных строится на основе стандартизированных интерфейсов и драйверов, реализующих доступ к конкретным СУБД. Основой является DB-API 2.0 (PEP 249) — спецификация, определяющая унифицированный интерфейс для работы с реляционными базами данных. Этот стандарт описывает поведение подключения (Connection), курсора (Cursor), выполнение запросов, обработку транзакций и исключений.
Согласно DB-API 2.0, типичный цикл работы включает:
- Установление соединения с базой данных;
- Создание объекта курсора;
- Выполнение SQL-запросов;
- Извлечение результатов;
- Подтверждение или откат изменений (в случае модификации);
- Закрытие соединения.
Поддержка DB-API 2.0 обеспечивается большинством популярных драйверов: sqlite3, psycopg2 (PostgreSQL), mysql-connector-python, pyodbc и другими.
SQLite — это встраиваемая реляционная СУБД, реализованная в виде библиотеки на C. В отличие от клиент-серверных решений (например, PostgreSQL или MySQL), она не требует запуска отдельного сервера: база данных хранится в одном файле на диске. Это делает её удобной для локального хранения данных, прототипирования, тестирования и встраивания в приложения.
Модуль sqlite3, входящий в стандартную библиотеку Python, предоставляет полную реализацию DB-API 2.0 для работы с SQLite. Он позволяет выполнять любые SQL-операции: DDL (определение схемы), DML (манипуляция данными), DQL (выборки), а также управлять транзакциями.
Пример создания таблицы пользователей:
import sqlite3
# Подключение к базе (создаётся файл, если отсутствует)
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
# Создание таблицы
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Вставка данных
cursor.execute('''
INSERT INTO users (username, email) VALUES (?, ?)
''', ('alice', 'alice@example.com'))
# Фиксация транзакции
conn.commit()
# Выборка
cursor.execute('SELECT * FROM users')
rows = cursor.fetchall()
for row in rows:
print(row)
# Закрытие соединения
conn.close()
Особенности sqlite3:
- Автоматическое управление транзакциями: по умолчанию каждый DML-запрос запускается в неявной транзакции, которую необходимо явно фиксировать через commit() или откатывать через rollback().
- Поддержка привязки параметров (parameter binding) для предотвращения SQL-инъекций.
- Возможность расширения функциональности через пользовательские функции, агрегаты и коллации.
Ограничения:
- Отсутствие многопользовательской записи при высокой нагрузке (из-за блокировки на уровне файла).
- Нет встроенной поддержки некоторых продвинутых возможностей, присущих серверным СУБД (например, оконные функции до версии 3.25, сложные триггеры и т.п.).
Для взаимодействия с внешними серверными СУБД используются сторонние драйверы, совместимые с DB-API 2.0.
psycopg2 — наиболее распространённый адаптер для PostgreSQL. Он поддерживает все возможности DB-API 2.0, а также расширенные функции: адаптацию типов Python → PostgreSQL, работу с курсорами, асинхронные операции (через psycopg2.extras.wait_select) и т.д.
import psycopg2
conn = psycopg2.connect(
host="localhost",
database="mydb",
user="user",
password="pass"
)
cursor = conn.cursor()
cursor.execute("SELECT version();")
print(cursor.fetchone())
conn.close()
mysql-connector-python — официальный драйвер от Oracle, реализует DB-API 2.0. PyMySQL — чистый Python-реализация, совместимая с MySQLdb. Оба позволяют работать с MySQL/MariaDB через стандартный интерфейс.
Для Microsoft SQL Server используется pyodbc или pymssql. Для Oracle — cx_Oracle (ныне oracledb). Все они следуют общей парадигме:
подключение → курсор → выполнение → извлечение → закрытие.
ORM — это программный слой, преобразующий данные между несовместимыми типовыми системами: объектами в памяти и реляционными таблицами в БД. ORM абстрагирует разработчика от написания «сырого» SQL, предоставляя интерфейс на уровне классов и объектов.
SQLAlchemy — наиболее гибкий и мощный ORM-инструмент в экосистеме Python. Он предлагает два уровня абстракции:
- Core — выражения на уровне SQL (SQL Expression Language), позволяющие строить запросы декларативно без написания строк.
- ORM — полноценный объектно-реляционный Mapper, работающий поверх Core.
Пример использования SQLAlchemy (Declarative Base):
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
email = Column(String(100), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
# Настройка движка и сессии
engine = create_engine('sqlite:///example.db')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# Добавление пользователя
new_user = User(username='bob', email='bob@example.com')
session.add(new_user)
session.commit()
# Запрос
users = session.query(User).filter(User.username == 'bob').all()
for user in users:
print(user.id, user.email)
session.close()
Особенности SQLAlchemy:
- Поддержка множества диалектов (SQLite, PostgreSQL, MySQL, Oracle и др.);
- Гибкость: можно комбинировать ORM и "сырой" SQL;
- Механизм миграций (через Alembic);
- Поддержка контекстного управления сессиями (через менеджеры контекста).
Django ORM — часть веб-фреймворка Django. Он тесно интегрирован с остальными компонентами фреймворка и ориентирован на быстрое развитие CRUD-приложений.
Пример модели:
from django.db import models
class User(models.Model):
username = models.CharField(max_length=50, unique=True)
email = models.EmailField()
created_at = models.DateTimeField(auto_now_add=True)
Запросы выполняются через менеджер objects:
User.objects.create(username='charlie', email='charlie@example.com')
users = User.objects.filter(username__startswith='c')
Особенности Django ORM:
- Простота в использовании, особенно в рамках Django-проекта;
- Автоматическая генерация схемы и миграций (makemigrations, migrate);
- Наличие встроенных методов для фильтрации, аннотаций, агрегаций;
- Ограниченная возможность выхода за рамки ORM — для сложных запросов требуется raw() или extra(). Однако, вне контекста Django использование ORM затруднено, так как он зависит от настроек проекта и django.setup().
Транзакция — последовательность операций, выполняемых как единое целое: либо все завершаются успешно (commit), либо ни одна не применяется (rollback). В Python транзакции управляются на уровне соединения.
При использовании DB-API 2.0, после изменения данных (INSERT, UPDATE, DELETE) необходимо вызвать conn.commit(), иначе изменения будут потеряны при закрытии соединения. При возникновении ошибки следует вызвать conn.rollback().
Рекомендуется использовать менеджеры контекста для автоматического управления транзакциями:
from contextlib import closing
with closing(sqlite3.connect('example.db')) as conn:
try:
cursor = conn.cursor()
cursor.execute("INSERT INTO users (username, email) VALUES (?, ?)", ('dave', 'dave@example.com'))
conn.commit()
except Exception:
conn.rollback()
raise
SQLAlchemy предоставляет более развитую модель управления сессиями и транзакциями. Сессия (Session) сама управляет транзакцией, и при использовании session.commit() изменения фиксируются, а session.rollback() — откатываются.
Python предоставляет широкий спектр средств для работы с базами данных — от низкоуровневого доступа через DB-API 2.0 до высокоуровневых ORM. Выбор инструмента зависит от масштаба приложения, требований к производительности, архитектуры системы и необходимости интеграции с другими компонентами. SQLite подходит для локального хранения и прототипирования, но не предназначен для масштабируемых многопользовательских систем. Серверные СУБД (PostgreSQL, MySQL) требуют установки дополнительных драйверов, но обеспечивают надёжность, отказоустойчивость и производительность. ORM, такие как SQLAlchemy и Django ORM, снижают необходимость в написании SQL, но требуют понимания того, какой код генерируется под капотом. Без этого возможны проблемы с производительностью и непредсказуемым поведением.