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 то, что должно вычисляться методом? Не попали ли в строковое представление секреты и токены?

Подписаться
Уведомить о
guest
0 комментариев
Старые
Новые Популярные

Настройки cookie

0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x