Python
Dataclasses в Python: пример класса
dataclass помогает быстро описывать классы, которые в основном хранят данные. Python сам создает __init__, понятное строковое представление и сравнение объектов. Это удобно для настроек, карточек товаров, результатов парсинга, DTO, небольших сущностей приложения и тестовых данных
Главное не воспринимать dataclass как замену любому классу. Если объект хранит данные и немного проверяет их — dataclass подходит. Если внутри много поведения, сложная бизнес-логика и скрытое состояние, обычный класс может быть понятнее
Минимальный пример
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
age: int
user = User("Анна", "anna@example.com", 28)
print(user)
print(user.email)
Без @dataclass пришлось бы вручную писать __init__. Здесь Python создает его сам на основе полей класса
Значения по умолчанию
Поле может иметь значение по умолчанию. Важно ставить обязательные поля раньше необязательных
from dataclasses import dataclass
@dataclass
class Product:
title: str
price: int
in_stock: bool = True
book = Product("Python для новичков", 1200)
print(book.in_stock)
Если поставить поле без значения после поля со значением, Python выдаст ошибку. Это то же правило, что и у аргументов функции
Списки и default_factory
Для изменяемых значений нельзя использовать обычный пустой список как значение по умолчанию. Используйте field(default_factory=list)
from dataclasses import dataclass, field
@dataclass
class Order:
number: str
items: list[str] = field(default_factory=list)
order = Order("A-100")
order.items.append("Книга")
print(order.items)
Так у каждого объекта будет собственный список. Без default_factory можно случайно получить общий список для разных объектов, а это почти всегда ошибка
Проверка данных через post_init
Если после создания объекта нужно проверить или подготовить поля, используйте __post_init__
from dataclasses import dataclass
@dataclass
class Lesson:
title: str
duration_min: int
def __post_init__(self):
if self.duration_min <= 0:
raise ValueError("Длительность должна быть больше нуля")
self.title = self.title.strip()
lesson = Lesson(" Введение в Python ", 45)
print(lesson.title)
__post_init__ запускается сразу после автоматически созданного __init__. Это удобное место для валидации, нормализации строк и расчетных полей
Преобразование в словарь
Для передачи данных дальше удобно использовать asdict()
from dataclasses import asdict, dataclass
@dataclass
class City:
name: str
population: int
city = City("Казань", 1300000)
print(asdict(city))
Результат — обычный словарь. Его можно сохранить в JSON, отправить в функцию или сравнить в тесте
Замороженный dataclass
Если объект после создания не должен меняться, добавьте frozen=True
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
point = Point(10, 20)
print(point.x)
При попытке изменить point.x Python выдаст ошибку. Это полезно для координат, настроек, ключей словаря и других значений, которые должны оставаться стабильными
Когда dataclass не нужен
Dataclass не нужен, если у вас просто словарь из двух полей и нет смысла создавать отдельный тип. Он также не решает задачу хранения в базе данных сам по себе. Для ORM есть свои модели, а dataclass чаще используют как легкий объект данных между слоями программы
Чек-лист
- поля класса описаны с типами
- обязательные поля стоят перед полями со значениями по умолчанию
- для списков и словарей используется
default_factory - проверка данных вынесена в
__post_init__ frozen=Trueиспользуется только там, где объект действительно должен быть неизменяемым
Сравнение объектов
Dataclass умеет сравнивать объекты по значениям полей
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
first = User(1, "Анна")
second = User(1, "Анна")
print(first == second)
Обычный класс без собственного __eq__ сравнивал бы объекты по идентичности, а dataclass сравнивает содержимое. Это удобно в тестах и при работе с небольшими объектами данных
Сортировка через order
Если нужно сортировать объекты, можно включить order=True
from dataclasses import dataclass
@dataclass(order=True)
class Task:
priority: int
title: str
tasks = [Task(3, "почта"), Task(1, "счет"), Task(2, "звонок")]
print(sorted(tasks))
Сортировка идет по полям в том порядке, в котором они объявлены. Поэтому поле priority поставлено первым
Поле, которое не участвует в repr
Иногда в объекте есть служебное значение, которое не нужно выводить в лог
from dataclasses import dataclass, field
@dataclass
class ApiClientConfig:
base_url: str
token: str = field(repr=False)
config = ApiClientConfig("https://api.example.com", "secret-token")
print(config)
Токен останется в объекте, но не попадет в строковое представление. Это не полноценная защита секрета, но хорошая привычка для логов
Dataclass и методы
Dataclass не запрещает методы. Можно хранить данные и добавить поведение, которое естественно относится к объекту
from dataclasses import dataclass
@dataclass
class CartItem:
title: str
price: int
quantity: int
def total(self):
return self.price * self.quantity
item = CartItem("Книга", 700, 2)
print(item.total())
Если методов становится много, проверьте, не превратился ли объект данных в полноценную бизнес-сущность. Иногда это нормально, иногда лучше разделить данные и сервисную логику
Пример для результата парсинга
Dataclass удобен, когда функция возвращает не один результат, а связанную группу данных
from dataclasses import dataclass
@dataclass
class ParsedLink:
text: str
href: str
external: bool
def parse_link(text, href):
return ParsedLink(
text=text.strip(),
href=href,
external=href.startswith("http"),
)
link = parse_link(" Документация ", "https://docs.python.org")
print(link.text, link.external)
Вместо словаря с неочевидными ключами вы получаете объект с понятными полями. Редактор кода лучше подсказывает имена, а ошибки в названии поля заметнее
По этой теме полезно отдельно посмотреть Составьте еженедельное расписание занятий, чтобы расширить контекст и сравнить подходы
По этой теме полезно отдельно посмотреть Аннотации типов для декораторов в Python, чтобы расширить контекст и сравнить подходы
Пример для настроек приложения
from dataclasses import dataclass
@dataclass(frozen=True)
class AppSettings:
debug: bool
page_size: int
language: str = "ru"
settings = AppSettings(debug=False, page_size=20)
frozen=True здесь уместен: настройки создали один раз и дальше только читают. Если нужно изменить настройки, лучше создать новый объект, а не менять существующий в разных местах программы
Что проверить в своем dataclass
Перед использованием посмотрите на объект глазами будущего читателя кода. Понятны ли поля без комментариев? Есть ли значение по умолчанию там, где оно действительно нужно? Не храните ли вы в dataclass то, что должно вычисляться методом? Не попали ли в строковое представление секреты и токены?