Каждому разработчику Python требовалось в какой-то момент создать класс, предназначенный для хранения данных. До сих пор при реализации такого класса им приходилось реализовывать многие базовые функции, такие как хеширование и сравнение, что в итоге приводило к груде шаблонного кода. Вот тут-то и пригодятся классы данных, чтобы упростить и сделать их более компактными.

Что такое класс данных?
Класс данных, как следует из его названия, - это класс, обычно используемый для хранения данных.
Классы данных содержат только поля и методы для доступа к ним (геттеры и сеттеры). Они просто действуют как контейнеры данных, используемые другими классами.
Классы данных в Python
Начиная с python 3.7, была представлена новая захватывающая функция - декоратор @dataclass через библиотеку Dataclasses.
Декоратор @dataclass используется для автоматического создания базовых функций для классов, включая __init__(), __hash__(), __repr__() и другие, что помогает сократить некоторый шаблонный код.
Например, украшение класса @dataclass позволит вам создавать, печатать и сравнивать его экземпляры прямо из коробки:
Классы данных можно рассматривать как «изменяемые именованные кортежи со значениями по умолчанию». Поскольку классы данных используют обычный синтаксис определения классов, вы можете свободно использовать inheritance, metaclasses, docstrings, определяемые пользователем методы, фабрики классов и другие функции классов Python.
Более подробный пример магии Dataclasses.
Давайте реализуем класс, представляющий книгу обычным способом.
Эта книга будет неизменной и сопоставимой с другими книгами.
Теперь давайте реализуем тот же класс с теми же функциями, но на этот раз с использованием Dataclasses.
Итак, как видите, с 30 строк кода осталось всего 7 строк (считая пустые строки для удобства чтения). Также представьте, что вы хотите добавить к классу новый атрибут. Используя обычный способ, вам нужно будет реорганизовать все методы, чтобы учесть новый атрибут в каждом из них, тогда как с Dataclasses вам нужно будет добавить еще одну строку с новый атрибут.
Параметры декоратора класса данных
Декоратор @dataclass имеет несколько параметров, которые вы можете использовать (например, параметр frozen в приведенном выше примере), эти параметры определяют характер класса:
init(Trueпо умолчанию) сгенерирует метод__init__, который, в свою очередь, инициализирует каждый атрибут в соответствии с его объявлением.repr(Trueпо умолчанию) сгенерирует метод__repr__, который, в свою очередь, сгенерирует строку повторения, которая будет иметь имя класса, имя и имя каждого поля в том порядке, в котором они определены в классе. Поля, отмеченные как исключенные из репортажа, не будут включены. Например: Книга (имя = ’Хоббит’, автор = ’Дж. Р. Р. Толкин’, num_pages = 310).eq(Trueпо умолчанию) сгенерирует метод__eq__, который по порядку сравнивает класс, как если бы он был кортежем его полей.order(Falseпо умолчанию), чтобы генерировать методы__lt__,__le__,__gt__и__ge__, которые позволяют сравнивать экземпляры классов. Если order равен true, а eq - false, возникает ошибка ValueError.frozen(Falseпо умолчанию), чтобы эмулировать замороженные экземпляры, доступные только для чтения. Другими словами, еслиTrue, делает объекты неизменяемыми (присвоение полей вызовет исключение), и поэтому они могут использоваться как ключи словаря.unsafe_hash(Falseпо умолчанию) определяет, как__hash__реализуется в соответствии сeqиfrozen. Еслиeqиfrozenоба равныTrue, классы данных сгенерируют для вас__hash__метод. ЕслиeqравноTrue, аfrozenравноFalse,__hash__будет установлено вNone, отмечая его как нехешируемый (что так и есть). ЕслиeqравноFalse,__hash__останется нетронутым, что означает, что будет использоваться метод__hash__суперкласса (если суперклассobject, это означает, что он вернется к хешированию на основе идентификатора).
Поля класса данных
Существует возможность установить значения по умолчанию для полей класса данных:
В некоторых случаях достаточно «простого» пути и использования condition: str = 'new', но в других случаях, когда для инициализации поля требуется более сложный метод, самое время использовать метод field().
Представьте, что вы хотите инициализировать поле типа List.
Использование field_name = [] или field_name = list() не поможет, поскольку конечным результатом их использования является список mutable, который будет использоваться всеми экземплярами класса:
В приведенном выше случае правильный способ - инициализировать поле friends (или любое другое поле типа List):
И конечный результат:
Параметры метода field():
default: Если указано, это будет значение по умолчанию для этого поля.default_factory: Если указано, это должен быть вызываемый объект без аргументов, который будет вызываться, когда для этого поля потребуется значение по умолчанию. Среди прочего, это можно использовать для указания полей с изменяемыми значениями по умолчанию.init: ЕслиTrue(по умолчанию), это поле включается в качестве параметра в сгенерированный__init__method.repr: еслиTrue(по умолчанию), это поле включается в строку, возвращаемую сгенерированным методом__repr__.compare: ЕслиTrue(по умолчанию), это поле включается в сгенерированные методы сравнения и сравнения (__eq__,__gt__и т. Д.).hash: это может бытьboolилиNone. ЕслиTrue, это поле включается в сгенерированный__hash__метод. ЕслиNone(по умолчанию), используйте значениеcompare.
Примечание. Одна из возможных причин для установки hash = False, но compare = True может заключаться в том, что для поля слишком дорого вычислять хеш-значение, это поле необходимо для проверки равенства, и есть другие поля, которые влияют на хэш типа. ценить. Даже если поле исключено из хеша, оно все равно будет использоваться для сравнения.
Заключение
При работе с классами, ориентированными на данные, Dataclasses - отличный инструмент.
С классами данных вам не нужно писать шаблонный код для правильной инициализации, представления и сравнения ваших объектов. Это поможет вам избежать лишнего набора текста и размышлений, сделав код менее подверженным ошибкам, поскольку он уже делает почти все за вас.
Могут быть добавлены и другие методы, как и в любом другом классе, но рекомендуется оставить основные функции под управлением Dataclasses.
О Dataclasses можно еще много поговорить, но это уже другая статья.