Обновить

Комментарии 15

Спасибо за статью, но мне кажется Вы учите детей плохому.

Если вместо числа приходит строка нужно выбрасывать TypeError, либо делать нормализацию данных перед тем как их использовать если источник данных мы не контролируем. Неявный кастинг — зло и источник ошибок.

С точки зрения производительности pydantic тот ещё тормоз. Мы выкинули его из всех проектов компании именно по причине низкой производительности. Заменили на Msgspec и ни разу не пожалели.

Спасибо за альтернативное мнение!

  1. Про неявный кастинг: Тут вопрос философии. Когда мы принимаем данные из JSON или query-параметров (где всё — строки), автоматическое приведение типов спасает от написания тонн бойлерплейта. Но если нужна строгость — в Pydantic V2 есть ConfigDict(strict=True), который будет кидать ошибки, как вы и описали.

  2. Про производительность: Вы, вероятно, вспоминаете Pydantic V1. Вторая версия (о которой статья) написана на Rust и в синтетических тестах приблизилась к msgspec (хотя msgspec всё еще быстрее, тут спорить глупо).

  1. json умеет и числа и строки

    {
      "str": "123",
      "int": 123,
      "float": 12.3
    }

    Тут проблемы могут быть только если мы не контролируем источник данных.

  2. Pydantic мы выкинули именно V2. Несмотря на Rust он как был тормозом так и остался. Вот пруф: https://jcristharif.com/msgspec/benchmarks.html. Tесты производительности на реальных проектах всё подтвердили.

Там ещё и фактор памяти появляется. На "старых raspberry pi" ограничитель вполне реальный.

Когда у вас миллионы объектов запихивать каждый из них в свою обёртку, так себе идея, для этого есть таблицы: pandas, polars, ...

вы абсолютно правы! Пытаться прогнать ETL-пайплайн на миллион записей через Pydantic (или любые Python-объекты в цикле) — это очень плохо. Для таких объемов есть векторные вычисления в Polars/Pandas.

Но у Pydantic другая ниша: это транзакционная логика. Когда к нам прилетает один сложный JSON-запрос на создание заказа со вложенными адресами и списками товаров, нам не нужен DataFrame. Нам нужен строгий валидированный объект, с которым удобно работать в бизнес-логике.

Pydantic — для API и конфигов. Polars — для "молотилок" данных. Инструменты разные, и задачи у них разные.

Так Pydatnic же для DTO нужен, dataclasses для доменного слоя. Не везде валидация нужна, в домене вообще лучше либо не использовать библиотеки, либо только из стандартной библиотеки Python (то есть как раз dataclasses или attrs)

Кстати сейчас msgspec появился, выглядит как неплохая замена Pydantic

Можно поподробнее про датаклассы в доменах? Я в своем проекте и в ДТО, и в доменах использую pydantic, вроде проблем нет + если какая-то ошибка в коде в репозитории, то на валидации это сразу всплывает и быстро отлаживается. Откуда такое ограничение?

Ну это плохо, потому что домен не должен зависеть от внешних библиотек (имеется ввиду, которые не входят в стандартную библиотеку). Также у тебя домен еще и берет на себя функцию валидации, такого не должно быть.

Лучше у ИИ спросить, я пока сам не сказать что гуру. Сам тоже пишу, но в домене dataclasses использую.

Домен может зависеть от внешних библиотек, просто это риск. Пидантик не выглядит пока как библиотека со стабильным поведением и апи, ещё свежа память переезда с первой версии. Валидация в домене - важная вещь, она там есть, но она отличается от валидации формы данных. В домене мы проверяем диапазоны значений, бизнес смысл этих значений по одному или вместе, но не проверяем что через апи нам передали строку вместо числа и не описываем, сериализовать дату с unittimestamp или iso format. Пидантик, будучи мощным инструментом, всё смешал в кучу.

Да, согласен. Но я бы всё равно старался не использовать внешние библиотеки

msgspec неплох, но очень слабо кастомизируется. Он очень быстр, но умеет мало что. Могу так же предложить наш проект adaptix

Очень основательное исследование, спасибо! На минутку даже показалось, что автор сам является одним из разработчиков модуля. 😊

Тем не менее, присоединюсь к предыдущим комментариям. PyDantic - это не замена dataclass, он решает другие задачи. Тяжело, сложно, монструозно. Сам по себе состоит из двух модулей, с внутренней сериализацией структур с Python в Rust, и обратно. К тому же, тянет за собой ещё 3 сторонних модуля...

Основное преимущество pydantic в том, что то, что он делает - делает неплохо и достаточно стабильно. Проверено годами. Плюс, его возможности относительно легко встроить в свою собственную мета фабрику классов. Это хороший плюс относительно альтернативных библиотек, которые создают изолированные объекты и общаются с внешним миром через свои специализированные конверторы.

# Получаем некорректные данные (цена в виде строки)
incoming_data = {"name": "Ноутбук", "price": "50000"}
item = Product(**incoming_data) # Ошибки нет, объект создан

ваша проблема в том, что вы путаете парсинг данных из недоверенного источника и конструирование объекта. Это две разные операции, которые должны делаться по разному. Авторы Pydantic это тоже путают и поэтому у вас при обынчом конструкировании происходит хз что (например совершенно неадекватно работают алиасы)

дядь, ты чет на пару лет со статьей опоздал

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации