Перейти к содержанию

Тема 11: Структура данных: Почему dict — это зло, и зачем обязательно использовать Pydantic для парсинга?

Новички любят словари (dict). "О, сервер вернул JSON, я просто сделаю data = response.json() и буду обращаться по ключам!"

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


1. Почему dict — это боль? (Синдром "Угадай Ключ")

Представь код:

def process_item(item):
    # Что такое item? Словарь. Какие там ключи? ХЗ.
    # Надо лезть в функцию парсинга или принтовать item, чтобы узнать.

    # Ошибка 1: Опечатка в ключе. Узнаешь только в рантайме.
    name = item['title'] # А на сайте сменили ключ на 'name' -> KeyError

    # Ошибка 2: Типы данных.
    price = item['price'] # Пришло "1000 руб" (str), а ты ждал 1000 (int)
    total = price * 2     # "1000 руб1000 руб" -> Логическая ошибка, скрипт не упал, но данные мусор.

Проблемы:

  1. Нет автодополнения (Autocomplete): IDE не знает, что лежит в словаре. Ты пишешь код вслепую.
  2. Нет валидации: Ты тащишь грязные данные (пробелы, null, строки вместо чисел) глубоко в бизнес-логику и базу данных.
  3. Хрупкость: Сменился API — всё упало в случайных местах.

2. Pydantic — "Железобетонный Контракт"

Pydantic — это библиотека, которая заставляет данные отвечать за базар. Ты описываешь Схему (класс), и библиотека сама проверяет, чистит и типизирует входящий мусор.

Как это выглядит:

from pydantic import BaseModel, HttpUrl, field_validator
from typing import Optional

# Мы описываем КОНТРАКТ.
# Если данные не подходят — они не пройдут дальше порога.
class Product(BaseModel):
    id: int                   # Сам сконвертирует "123" в 123
    title: str
    price: float              # Сам сделает float
    url: HttpUrl              # Проверит, что это валидная ссылка
    is_available: bool = True # Дефолтное значение

    # Можно писать свои чистильщики (Validators)
    @field_validator('title')
    def clean_title(cls, v):
        return v.strip().upper() # Убрали пробелы, капсом

# --- В БОЮ ---

raw_data = {
    "id": "555",             # Строка!
    "title": "  iphone  ",   # Пробелы!
    "price": 100.50,
    "url": "<https://apple.com>"
}

# Магия:
item = Product(**raw_data)

print(item.id)     # 555 (int) - Тип исправлен!
print(item.title)  # "IPHONE" - Очищено!
print(item.price)  # 100.5 (float)

# IDE подсказывает: item. (и выпадают поля id, title, price)

3. Почему Архитектор требует Pydantic?

  1. Fail Fast (Падай быстро): Если сайт изменил API и начал слать цену как null, Pydantic выкинет ошибку сразу при создании объекта. Ты узнаешь о проблеме на этапе парсинга, а не когда будешь записывать данные в БД и получишь краш.
  2. IDE — твой друг: Когда ты пишешь item.price, IDE знает, что это float. Она подсветит ошибку, если ты попробуешь сделать item.price.upper(). В словарях IDE молчит как партизан.
  3. Вложенность (Nesting): JSON часто бывает сложным (список вариантов, характеристики). На Pydantic это описывается красиво:

    class Variant(BaseModel):
        color: str
        size: str
    
    class Product(BaseModel):
        name: str
        variants: list[Variant] # Список объектов, а не список словарей!
    

    Теперь ты обращаешься product.variants[0].color. Чистота.

  4. Экспорт: Нужно сохранить в файл? item.model_dump_json() — и у тебя готовая JSON-строка. item.model_dump() — готовый словарь для вставки в MongoDB.


🧠 Резюме:

  • Использовать dict для передачи данных между функциями — это говнокод.
  • Любой парсер должен начинаться с файла models.py, где описаны классы данных.
  • Парсинг выглядит так:
    1. Получил грязный HTML/JSON.
    2. Вытащил сырые данные.
    3. Тут же запихнул их в Pydantic-модель.
    4. Дальше работаешь ТОЛЬКО с объектом модели.