Как стать автором
Обновить
1570.78
МТС
Про жизнь и развитие в IT

Msgspec vs DataClasses: битва инструментов в мире Python-сериализации

Время на прочтение6 мин
Количество просмотров866

Хабрчане, привет! Это Леша Жиряков из МТС Диджитал. Недавно я писал про FastAPI vs Litestar и Polars vs Pandas, а сегодня разберем два популярных инструмента — Msgspec и DataClasses. Оба помогают структурировать данные, добавить энтерпрайзности в проект, но подходы у них разные. Какой из них быстрее и удобнее, где их лучше применять? Давайте разбираться.

DataClasses: классика жанра

Источник

DataClasses — встроенный инструмент Python, представленный в стандартной библиотеке начиная с версии 3.7. Он создан для упрощения и автоматизации работы с классами, которые в основном используются для хранения и обмена данными. А еще улучшает типизацию в более-менее серьезных проектах.

По своей сути это декоратор, который автоматически генерирует методы init, repr, eq и другие для вашего класса. Это избавляет разработчика от написания шаблонного кода и экономит гору времени.

Где применяется

  • Структурирование данных: DataClasses идеально подходит для создания DTO (Data Transfer Objects) или моделей данных.

  • Читаемость кода: упрощает понимание структуры данных за счет минималистичного синтаксиса.

  • Интеграция с другими инструментами: отлично работает с ORM, API и прочими библиотеками.

А теперь — пример использования DataClasses. Допустим, нам нужно создать DTO для пользователя:

from dataclasses import dataclass

@dataclass
class User:
  first_name: str
  last_name: str
  email: str
  age: int

Попробуем создать объект:

>>> User("Alyosha", "Popovich", "aleshap@something.com", "16") 
User(first_name='Alyosha', last_name='Popovich', email='aleshap@something.com', age='16')

Получилось, но возраст должен быть int, а не str. И ошибки валидации мы не увидели. Исправим это при помощи метода __post_init__.

from dataclasses import dataclass

@dataclass
class User:
  first_name: str
  last_name: str
  email: str
  age: int

  def __post_init__(self):
     if not isinstance(self.age, int):
        raise Exception("Age must be an integer")

Попробуем еще раз:

>>> User("Alyosha", "Popovich", "aleshap@something.com", "16")
Traceback (most recent call last):
  File "<python-input-13>", line 1, in <module>
    User("Alyosha", "Popovich", "aleshap@something.com", "16")
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 7, in __init__
  File "<python-input-11>", line 10, in __post_init__
    raise Exception("Age must be an integer")
Exception: Age must be an integer

Ура! Теперь у нас работает валидация. А еще мы можем «заморозить» наш класс при помощи frozen=True:

from dataclasses import dataclass
@dataclass(frozen=True)
class User:
  first_name: str
  last_name: str
  email: str
  age: int

  def __post_init__(self):
     if not isinstance(self.age, int):
        raise Exception("Age must be an integer")

Тогда нельзя будет изменить атрибуты экземпляра класса после его инициализации

>>> user_1 = User("Alyosha", "Popovich", "aleshap@something.com", 16)
>>> user_1.first_name
'Alyosha'
>>> user_1.first_name = "Иван"
Traceback (most recent call last):
  File "<python-input-13>", line 1, in <module>
    user_1.first_name = "Иван"
    ^^^^^^^^^^^^^^^^^
  File "<string>", line 19, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'first_name'

Производительность

Скорость сериализации и десериализации. DataClasses не предназначен для высокоскоростной сериализации. Он использует стандартные механизмы Python для преобразования объектов в словари или JSON. Это удобно, но не быстро.

  • Сериализация: преобразование объектов dataclasses в JSON или словарь выполняется встроенными методами Python, например dataclasses.asdict(). Но метод рекурсивно копирует данные — а это может привести к увеличенному потреблению памяти и снижению производительности на больших структурах.

  • Десериализация: обратный процесс (из JSON или словаря в объект) тоже требует дополнительных шагов: создание экземпляра класса и заполнение его полей.

Использование памяти. DataClasses создает объекты, которые хранят данные в виде атрибутов. Это удобно, но не всегда эффективно с точки зрения памяти.

  • Каждый объект DataClasses содержит метаданные (например, имена полей). Это увеличивает объем используемой памяти.

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

Нагрузка на CPU. DataClasses полагается на стандартные механизмы Python, они не всегда оптимизированы для производительности.

  • Сериализация и десериализация требуют множества операций, таких как рекурсивный обход объектов. Это увеличивает нагрузку на CPU.

  • Использование встроенных функций Python (например, json.dumps() или DataClasseses.asdict()) добавляет накладные расходы.

DataClasses не оптимизирован для высокой производительности при сериализациии и десериализации. Он создан для удобства, а не скорости.

Msgspec: скорость и минимализм

Источник

Msgspec создал разработчик Джим Крист-Хариф (Jim Crist-Harif). Это библиотека для сериализации и десериализации данных с акцентом на производительность. Поддерживает форматы JSON, MessagePack и другие. Msgspec позволяет создавать типизированные структуры данных, похожие на DataClasses, но с фокусом на скорости.

Где применяется

  • Высоконагруженные системы: Msgspec идеально подходит для микросервисов, где важна скорость обработки данных.

  • Сериализация больших объемов данных: если вам нужно быстро упаковать или распаковать данные, Msgspec справится лучше многих аналогов.

  • Минималистичные проекты: Msgspec легковесен и не требует сложных зависимостей.

Повторим пример выше, только теперь для Msgspec:

import msgspec

class User(msgspec.Struct):
   first_name: str
   last_name: str
   email: str
   age: int

Инициализируем объект и указываем вместо целого числа для параметра age — строку.

>>> User("Alyosha", "Popovich", "aleshap@something.com", "16")
User(first_name='Alyosha', last_name='Popovich', email='aleshap@something.com', age='16')

Объект создается и ошибки не возникает.

Но у Msgspec есть одна особенность — типы данных проверяются в момент декодирования. Например, вот что получим, если выполним декодирование:

>>> msgspec.json.decode(b'{"first_name": "Alyosha", "last_name": "Popovich", "email": "aleshap@something.com", "age": "16"}', type=User)
Traceback (most recent call last):
  File "<python-input-8>", line 1, in <module>
    msgspec.json.decode(b'{"first_name": "Alyosha", "last_name": "Popovich", "email": "aleshap@something.com", "age": "16"}', type=User)
    ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `int`, got `str` - at `$.age`

У нас ошибка валидации на месте age.

Как можно заметить, при самом процессе инициализации никакой валидации нет. Тут тоже можно воспользоваться методом __post_init__.

import msgspec

class User(msgspec.Struct):
   first_name: str
   last_name: str
   email: str
   age: int

   def __post_init__(self):
       if not isinstance(self.age, int):
           raise Exception("Age must be an integer")

При создании объекта с некорректным типом данных у поля age получаем ошибку:

>>> User("Alyosha", "Popovich", "aleshap@something.com", "16")
Traceback (most recent call last):
  File "<python-input-3>", line 1, in <module>
    User("Alyosha", "Popovich", "aleshap@something.com", "16")
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<python-input-2>", line 9, in __post_init__
    raise Exception("Age must be an integer")
Exception: Age must be an integer.

Скорость сериализации и десериализации. Msgspec разработан с акцентом на производительность. Он использует оптимизированные алгоритмы для сериализации и десериализации данных.

  • Сериализация: Msgspec напрямую преобразует объекты в бинарные форматы (например, MessagePack) или JSON, минимизируя использование памяти и CPU.

  • Десериализация: процесс обратного преобразования оптимизирован, так как Msgspec применяет низкоуровневые операции для обработки данных.

Использование памяти. Msgspec оптимизирован для работы с памятью. Он использует компактные структуры данных и минимизирует накладные расходы.

  • Объекты Msgspec хранят данные в более компактном формате, а это снижает использование памяти.

  • При сериализации Msgspec избегает создания промежуточных структур, что экономит память.

Нагрузка на CPU. Msgspec использует низкоуровневые оптимизации, чтобы снизить нагрузку на CPU:

  • Алгоритмы Msgspec минимизируют количество операций, необходимых для сериализации и десериализации.

  • Библиотека написана на C (или использует C-расширения) и работает быстро.

Msgspec гораздо быстрее DataClasses — за счет оптимизированного низкоуровневого кода и минималистичного подхода.

Что в итоге

Вот сравнительная таблица производительности между msgspec.Struct и стандартными Python dataclass. Данные взяты из официальных бенчмарков msgspec и дополнительных тестов:

Операция

msgspec.Struct

dataclass

Разница

Импорт
класса (мкс)

12.51

506.09

~40x

Создание
экземпляра (мкс)

0.09

0.36

~4x

Сравнение
на равенство (мкс)

0.02

0.14

~7x

Сравнение
порядка (мкс)

0.03

0.16

~5x

Мкс — микросекунды

Результаты показывают, что msgspec.Struct превосходит стандартные dataclass по всем измеренным параметрам. Так что выбор между ними зависит от особенностей вашего проекта и личных предпочтений.

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

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

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

Что еще почитать

Теги:
Хабы:
+13
Комментарии1

Полезные ссылки

Материнство в ИТ. Как подготовиться к новой роли — и есть ли путь обратно

Время на прочтение9 мин
Количество просмотров451
Всего голосов 9: ↑8 и ↓1+10
Комментарии7

Обработка ошибок REST API: лучшие практики

Уровень сложностиПростой
Время на прочтение5 мин
Количество просмотров5.4K
Всего голосов 19: ↑15 и ↓4+20
Комментарии14

Удивительный мир хакатонов: как я придумал для студентов задачку и что они с ней натворили

Время на прочтение6 мин
Количество просмотров1.9K
Всего голосов 9: ↑8 и ↓1+12
Комментарии0

Apache Flink: использование и автоматическая проверка собственного сериализатора состояния

Уровень сложностиСложный
Время на прочтение11 мин
Количество просмотров857
Всего голосов 7: ↑7 и ↓0+14
Комментарии0

Хаос vs один понятный флоу на все команды. Сказ о том, как в МТС производственный процесс внедряли. Часть 2

Время на прочтение6 мин
Количество просмотров974
Всего голосов 6: ↑6 и ↓0+10
Комментарии1

Информация

Сайт
www.mts.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия