TGViewer
Библиотека питониста | Python, Django, Flask Библиотека питониста | Python, Django, Flask @pyproglib · 37.3K subscribers
Post #7457 3.47K
🚨 Баг на ровном месте: как NaN превратился в None и сломал Pydantic

Недавно наткнулись на очень показательный баг в Python-проекте с распределёнными процессами.

Один алгоритм считает числовые пороги (`float`) и передаёт их другому процессу в виде JSON. Модели общие, всё на Pydantic — красота, типизация, валидация.

И вдруг второй процесс падает с ошибкой:

> Input should be a valid number, input_value=None

Хотя первый процесс отправлял… float.

🧩 Что произошло

В одном из порогов оказалось значение NaN:

thr = Thresholds(high=float('nan'), low=0.0)
json_str = thr.model_dump_json()
print(json_str)
# {"high": null, "low": 0.0}


🧩 Почему NaN стал null

Потому что:
— В IEEE 754 у float есть спецзначение NaN
— В JSON стандарте — нет представления для NaN
— Разрешены только: число, строка, объект, массив, true, false, null

Поэтому сериализатор делает единственное, что может — превращает NaN → null.

А дальше:

JSON null → Python None


И Pydantic получает:

high=None


Но поле объявлено как float, а не float | None → валидация падает.

То есть:

float('nan') → JSON null → Python None


Сериализация и десериализация не являются обратными операциями.

🧩 Почему это особенно коварно

NaN — валидный float. Его часто используют как “значение не определено”.

И у него особая логика:

nan > 10 # False
nan < 10 # False
nan == 10 # False


А вот None — это уже другой тип, и сравнения просто падают с TypeError. Семантика теряется.

🧩
Как это чинить

1. Можно сказать Pydantic сериализовать NaN как литерал:

{"high": NaN}



model_config = ConfigDict(ser_json_inf_nan='constants')


Работает идеально внутри Python.

Но многие парсеры (JS, БД, API) такой JSON не примут, и формально это уже не RFC-совместимый JSON.

2. Сделать поле float | None


high: float | None


JSON становится валидным. Но теперь теряется типовая строгость и нужно везде помнить, что None на самом деле значит NaN.

3. Кастомный тип (самый аккуратный)

Сказать модели явно:
> null в JSON — это NaN, а не None


FloatNaN = Annotated[
float,
PlainSerializer(lambda v: None if math.isnan(v) else v, when_used="json"),
BeforeValidator(lambda v: float("nan") if v is None else v),
]


Теперь:

NaN → null → NaN


📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека питониста

#буст
  • 👍 12
  • ❤ 7
More from @pyproglib
  1. Sep 30, 2026🔥 Последний день для разработчиков AI уже пишет код. Теперь важнее научиться встраивать е…
  2. Sep 25, 2026🔥 Курс «ИИ для разработчиков» уже стартовал И мы решили открыть первое занятие всем — мож…
  3. Sep 20, 2026👻 94% токенов уходят не на код Анализ запусков показал: основная часть токенов уходит на…
  4. Sep 19, 2026📚Напоминаем про наш полный курс «Самоучитель по Python для начинающих» Мы написали и собр…
  5. Sep 15, 2026😭 Как не потратить недельный лимит AI-кодинга за три дня? Разберём на вебинаре, как трати…
  6. Sep 15, 2026⏱️ Справочник по Big-O в Python Для тех, кто хочет писать не просто работающий, а эффектив…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →