Урок 84. MongoDB и Python. Модули

📁 Блок: Базы данных и модули ⏱️ Время изучения: ~90 мин 🎯 Сложность: Средняя
#close #find #sort #list #in #import #from

⚡ Кратко: суть темы

MongoDB и Python — работаем с документоориентированной базой данных через библиотеку pymongo: подключаемся, добавляем, читаем, обновляем и удаляем документы.

  • Устанавливаем pip install pymongo и подключаемся через MongoClient(...).
  • База и коллекция выбираются как client["db_name"] и db["collection_name"].
  • Документы — обычные Python-словари. CRUD-методы: insert_one, insert_many, find_one, find, update_one, update_many, delete_one, delete_many.
  • Обрабатывай ошибки через pymongo.errors, а не голый Exception.
  • Модуль — любой .py файл. Пакет — папка с модулями и __init__.py. Используй if __name__ == "__main__", чтобы код не выполнялся при импорте.

Что запомнить: MongoDB не требует заранее заданной схемы, но документы нужно правильно фильтровать словарями-условиями.

Частая ошибка: перепутать фильтр и проекцию в find() или забыть, что find() возвращает курсор, а не список.

🗄️ MongoDB — документоориентированная база данных

Реляционные базы данных хранят данные в таблицах со строгой схемой: нужно заранее описать столбцы и их типы. MongoDB работает иначе: данные хранятся в документах — JSON-подобных записях, которые в Python представляются обычными словарями dict.

Такой подход удобен, когда:

  • структура данных может меняться;
  • записи содержат вложенные объекты или списки;
  • нужно быстро прототипировать приложение без создания таблиц.

🔌 Подключение к MongoDB из Python

Для работы с MongoDB в Python используется библиотека pymongo. Установите её через pip:

pip install pymongo

Чтобы подключиться к серверу, создаём объект MongoClient и передаём строку подключения. В строке указываются логин, пароль, адрес сервера и параметры авторизации:

from pymongo import MongoClient

client = MongoClient(
    "mongodb://ich_editor:verystrongpassword"
    "@mongo.itcareerhub.de/?readPreference=primary"
    "&ssl=false&authMechanism=DEFAULT&authSource=ich_edit"
)

Основные параметры строки подключения:

  • ich_editor — имя пользователя;
  • verystrongpassword — пароль;
  • mongo.itcareerhub.de — адрес сервера;
  • authSource=ich_edit — база данных, в которой хранятся учётные данные.

Проверить подключение можно командой ping:

client.admin.command("ping")
print("Connection successful!")

🗂️ Выбор базы данных и коллекции

Базу данных и коллекцию можно выбрать через квадратные скобки:

db = client["ich_edit"]
products = db["products"]

Особенности MongoDB:

  • Если база данных не существует, она создаётся автоматически при первой записи.
  • Если коллекция не существует, она тоже создаётся при первой записи.
  • Подключение устанавливается лениво — реальное соединение происходит при первом запросе.

В конце работы соединение можно закрыть вручную:

client.close()

➕ Добавление данных

В MongoDB данные добавляются в виде словарей. Можно добавить один документ или сразу несколько:

# Один документ
product = {"name": "Notebook", "price": 5.99, "stock": 120}
result = products.insert_one(product)
print("Inserted ID:", result.inserted_id)

# Несколько документов
items = [
    {"name": "Pen", "price": 1.50, "stock": 300},
    {"name": "Pencil", "price": 0.99, "stock": 500},
]
result = products.insert_many(items)
print("Inserted IDs:", result.inserted_ids)

Если поле _id не указано, MongoDB создаёт его автоматически. В отличие от SQL, не нужно заранее описывать структуру коллекции.

🔍 Чтение данных

Для чтения используются методы find_one() и find():

doc = products.find_one()                 # первый документ или None
docs = products.find({"price": {"$lt": 5}})  # курсор с фильтром

for item in docs:
    print(item)

Метод find() возвращает не список, а курсор — объект-итератор. Это позволяет обходить документы по одному, не загружая всё в память сразу. Курсор поддерживает методы sort(), skip() и limit():

for doc in products.find().sort("price", -1).skip(1).limit(2):
    print(doc)

Чтобы ограничить набор возвращаемых полей, используется проекция:

# Только name, price и _id (по умолчанию)
for doc in products.find({}, {"name": 1, "price": 1}):
    print(doc)

# Все поля, кроме _id
for doc in products.find({}, {"_id": 0}):
    print(doc)

✏️ Обновление данных

Для изменения документов используются update_one() и update_many(). Первый аргумент — фильтр, второй — модификатор:

# Изменить один документ
result = products.update_one(
    {"name": "Notebook"},
    {"$set": {"price": 24.99}}
)
print("Matched:", result.matched_count)
print("Modified:", result.modified_count)

# Увеличить цену всех товаров на 1
result = products.update_many(
    {},
    {"$inc": {"price": 1}}
)

🗑️ Удаление данных

Удаление работает аналогично обновлению: один документ или все подходящие под условие:

result = products.delete_one({"name": "Notebook"})
print("Deleted:", result.deleted_count)

result = products.delete_many({"price": {"$lt": 2}})
print("Deleted:", result.deleted_count)

⚠️ Обработка ошибок

При работе с MongoDB могут возникать ошибки подключения, авторизации или запросов. Их нужно перехватывать через try ... except:

from pymongo import MongoClient, errors

try:
    client = MongoClient("mongodb://ich_editor:wrong_pass@mongo.itcareerhub.de/?authSource=ich_edit")
    db = client["store"]
    products = db["products"]
    products.insert_one({"name": "Lamp", "price": 15.99})
except errors.ConnectionFailure:
    print("Ошибка подключения к MongoDB")
except errors.OperationFailure:
    print("Ошибка авторизации или запроса")

Основные исключения:

  • errors.ConnectionFailure — ошибка подключения к серверу;
  • errors.OperationFailure — ошибка выполнения запроса или неправильные права;
  • errors.DuplicateKeyError — попытка вставить документ с уже существующим _id;
  • errors.PyMongoError — базовый класс для всех исключений pymongo.

📦 Модули в Python

Модуль — это любой файл с расширением .py, содержащий переменные, функции, классы и другие конструкции Python.

Модули позволяют:

  • разделять код по смыслу;
  • повторно использовать готовые решения;
  • использовать стандартную библиотеку Python (math, random, datetime);
  • устанавливать сторонние библиотеки через pip (pymongo, requests и др.);
  • упрощать тестирование и поддержку кода.

Виды модулей:

ТипПримерыКак получить
Встроенныеmath, random, datetimeВходят в состав Python
Сторонниеpymongo, requestsУстанавливаются через pip
ПользовательскиеЛюбые .py-файлыПишутся самостоятельно

Пример пользовательского модуля math_utils.py:

def average(numbers):
    return sum(numbers) / len(numbers)

def maximum(numbers):
    return max(numbers)

Использование в другом файле:

import math_utils
from math_utils import maximum

values = [10, 20, 30]
print(math_utils.average(values))
print(maximum(values))

🚀 Прямой запуск модуля

Конструкция if __name__ == "__main__" позволяет отделить код, который выполняется только при прямом запуске файла, от кода, который работает при импорте:

def average(numbers):
    return sum(numbers) / len(numbers)

if __name__ == "__main__":
    # Этот блок выполнится только при запуске файла напрямую
    print(average([1, 2, 3]))

При импорте модуля переменная __name__ равна имени модуля. При прямом запуске она равна "__main__".

⚙️ Компиляция модулей и __pycache__

Когда Python импортирует модуль, он компилирует его в байт-код и сохраняет в файл .pyc внутри папки __pycache__:

__pycache__/math_utils.cpython-312.pyc

Байт-код загружается быстрее, чем исходный .py файл. Файл .pyc пересоздаётся:

  • при первом импорте модуля;
  • если изменился исходный .py файл;
  • если обновилась версия Python.

📁 Пакеты и папки

Пакет — это папка, содержащая модули и файл __init__.py. Пакеты позволяют группировать модули по смыслу и создавать вложенную структуру.

Пример структуры:

modules/
├── main.py
├── math_utils.py
├── analyzer.py
└── tools/
    ├── __init__.py
    ├── text_utils.py
    └── helpers/
        ├── __init__.py
        └── string_tools.py

Файл __init__.py может быть пустым, но часто в нём делают удобные импорты:

# tools/__init__.py
from .text_utils import count_words
from .helpers.string_tools import reverse

Тогда вне пакета можно писать:

import tools

print(tools.count_words("Hello world"))
print(tools.reverse("Python"))
⚠️ Проверить по документации: в Python 3.3+ папка без __init__.py может считаться пространством имён (namespace package), но для обычных учебных проектов файл __init__.py остаётся стандартом.

✨ Современные практики

⚠️ Проверить по документации: рекомендации ниже выходят за рамки исходной лекции. В рабочих проектах пароли хранят в переменных окружения, а для сложных структур данных используют ODM (например, mongoengine или beanie).
  • Храните пароли и логины в переменных окружения, а не в коде.
  • Для локальной разработки удобна библиотека python-dotenv: положите секреты в файл .env и загрузите через load_dotenv().
  • Закрывайте соединение с MongoDB через client.close() или используйте контекстный менеджер.
  • Разделяйте код на модули: подключение к базе, бизнес-логика и запуск приложения — в разных файлах.
  • Используйте if __name__ == "__main__", чтобы тестовый код не выполнялся при импорте.