Python dataclasses: создание классов данных

17 июля 2026
Автор

Антон Ларичев

Что такое dataclasses

Модуль dataclasses, появившийся в Python 3.7, решает одну из самых распространённых задач — создание классов, которые в первую очередь хранят данные. Вместо того чтобы вручную писать __init__, __repr__ и __eq__, достаточно описать поля класса, и Python сгенерирует все эти методы автоматически.

Сравните обычный класс:

class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

    def __eq__(self, other):
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y

И аналогичный dataclass:

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

Оба варианта дают одинаковый результат, но dataclass в четыре раза короче.

Базовый синтаксис

Декоратор @dataclass применяется к классу с аннотированными полями. Аннотации типов обязательны — без них Python не распознает поле как атрибут dataclass.

from dataclasses import dataclass
from typing import Optional

@dataclass
class User:
    id: int
    username: str
    email: str
    is_active: bool = True
    bio: Optional[str] = None

Что Python генерирует автоматически:

  • __init__ — конструктор со всеми полями в качестве параметров
  • __repr__ — строковое представление объекта
  • __eq__ — сравнение по значениям всех полей
user1 = User(id=1, username="alice", email="alice@example.com")
user2 = User(id=1, username="alice", email="alice@example.com")

print(user1)          # User(id=1, username='alice', email='alice@example.com', is_active=True, bio=None)
print(user1 == user2) # True

Значения по умолчанию

Поля без значений по умолчанию должны идти перед полями с дефолтами — так же, как аргументы функции.

@dataclass
class Product:
    name: str          # обязательное поле
    price: float       # обязательное поле
    in_stock: bool = True
    quantity: int = 0

Для изменяемых значений по умолчанию (список, словарь) нельзя указывать их напрямую. Вместо этого используется field с параметром default_factory:

from dataclasses import dataclass, field

@dataclass
class Order:
    order_id: int
    items: list = field(default_factory=list)
    metadata: dict = field(default_factory=dict)

order = Order(order_id=42)
order.items.append("book")
print(order)  # Order(order_id=42, items=['book'], metadata={})

Если написать items: list = [], Python выбросит ValueError — это защита от классической ошибки с разделяемыми изменяемыми объектами.

Функция field и её параметры

Функция field() даёт тонкий контроль над поведением отдельных полей.

from dataclasses import dataclass, field

@dataclass
class Article:
    title: str
    content: str
    tags: list = field(default_factory=list)
    _word_count: int = field(init=False, repr=False)

    def __post_init__(self):
        self._word_count = len(self.content.split())

Основные параметры field():

  • default — значение по умолчанию
  • default_factory — функция без аргументов, возвращающая значение по умолчанию
  • init — включать ли поле в __init__ (по умолчанию True)
  • repr — включать ли поле в __repr__ (по умолчанию True)
  • compare — использовать ли поле при сравнении (по умолчанию True)
  • hash — включать ли поле в __hash__
@dataclass
class Employee:
    name: str
    salary: float = field(repr=False, compare=False)
    department: str = "Engineering"
    _internal_id: str = field(init=False, repr=False, default="")

    def __post_init__(self):
        self._internal_id = f"{self.department}-{self.name.lower()}"

Метод post_init

Когда нужно выполнить дополнительную инициализацию после того, как __init__ заполнил поля, используется __post_init__:

from dataclasses import dataclass
from datetime import datetime

@dataclass
class Event:
    name: str
    start: datetime
    end: datetime
    duration_hours: float = field(init=False)

    def __post_init__(self):
        if self.end <= self.start:
            raise ValueError("Дата окончания должна быть позже даты начала")
        delta = self.end - self.start
        self.duration_hours = delta.total_seconds() / 3600

event = Event(
    name="Конференция",
    start=datetime(2024, 6, 1, 9, 0),
    end=datetime(2024, 6, 1, 18, 0)
)
print(event.duration_hours)  # 9.0

Инициализация полей-переменных с init=False

Если поле исключено из __init__, его нужно вычислить в __post_init__. Для этого не нужно передавать значение в field(default=...) — достаточно присвоить в методе.

Параметры декоратора @dataclass

Декоратор принимает несколько именованных аргументов, которые управляют генерацией методов.

@dataclass(order=True, frozen=True)
class Version:
    major: int
    minor: int
    patch: int

Важные параметры:

  • eq=True — генерировать __eq__ (по умолчанию True)
  • order=False — генерировать методы сравнения __lt__, __le__, __gt__, __ge__
  • frozen=False — запретить изменение атрибутов после создания
  • slots=False — использовать __slots__ (Python 3.10+)
  • kw_only=False — все поля принимаются только как keyword-аргументы (Python 3.10+)

Параметр order

@dataclass(order=True)
class Version:
    major: int
    minor: int
    patch: int

v1 = Version(1, 9, 0)
v2 = Version(2, 0, 0)

print(v1 < v2)   # True
print(sorted([Version(1, 2, 3), Version(1, 0, 0), Version(2, 0, 0)]))
# [Version(major=1, minor=0, patch=0), Version(major=1, minor=2, patch=3), Version(major=2, minor=0, patch=0)]

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

Параметр frozen

Замороженный dataclass делает объект неизменяемым — попытка присвоить значение атрибуту выбросит FrozenInstanceError.

@dataclass(frozen=True)
class Coordinate:
    latitude: float
    longitude: float

coord = Coordinate(55.75, 37.62)
# coord.latitude = 0  # FrozenInstanceError

# Замороженные объекты можно хешировать и использовать как ключи словаря
location_cache = {coord: "Москва"}
print(location_cache[Coordinate(55.75, 37.62)])  # Москва

Наследование dataclasses

Dataclass может наследовать другой dataclass. Поля родителя добавляются перед полями дочернего класса.

from dataclasses import dataclass

@dataclass
class Animal:
    name: str
    age: int

@dataclass
class Dog(Animal):
    breed: str
    is_trained: bool = False

dog = Dog(name="Рекс", age=3, breed="Лабрадор")
print(dog)  # Dog(name='Рекс', age=3, breed='Лабрадор', is_trained=False)

Важный момент: если у родительского класса есть поля со значениями по умолчанию, все поля дочернего класса тоже должны иметь значения по умолчанию. Иначе Python выбросит TypeError — поля без дефолта не могут идти после полей с дефолтом.

@dataclass
class Base:
    x: int = 0  # есть дефолт

# Ошибка: поле без дефолта после поля с дефолтом
# @dataclass
# class Child(Base):
#     y: int  # TypeError!

@dataclass
class Child(Base):
    y: int = 0  # OK

Вспомогательные функции модуля

asdict и astuple

from dataclasses import dataclass, asdict, astuple

@dataclass
class RGB:
    red: int
    green: int
    blue: int

color = RGB(255, 128, 0)

print(asdict(color))   # {'red': 255, 'green': 128, 'blue': 0}
print(astuple(color))  # (255, 128, 0)

asdict рекурсивно обходит вложенные dataclasses и преобразует их в словари — удобно для сериализации в JSON.

import json
from dataclasses import dataclass, asdict
from typing import List

@dataclass
class Address:
    city: str
    street: str

@dataclass
class Person:
    name: str
    address: Address
    phones: List[str]

person = Person(
    name="Иван",
    address=Address(city="Москва", street="Арбат"),
    phones=["+7-999-000-00-00"]
)

print(json.dumps(asdict(person), ensure_ascii=False))
# {"name": "Иван", "address": {"city": "Москва", "street": "Арбат"}, "phones": ["+7-999-000-00-00"]}

replace

Функция replace создаёт копию объекта с изменёнными полями — незаменима при работе с замороженными dataclasses.

from dataclasses import dataclass, replace

@dataclass(frozen=True)
class Config:
    host: str
    port: int
    debug: bool = False

base_config = Config(host="localhost", port=8000)
dev_config = replace(base_config, debug=True)
prod_config = replace(base_config, host="0.0.0.0", port=80)

print(base_config)  # Config(host='localhost', port=8000, debug=False)
print(dev_config)   # Config(host='localhost', port=8000, debug=True)
print(prod_config)  # Config(host='0.0.0.0', port=80, debug=False)

fields

Функция fields возвращает кортеж объектов Field, описывающих поля класса:

from dataclasses import dataclass, fields

@dataclass
class Point:
    x: float
    y: float
    label: str = ""

for f in fields(Point):
    print(f.name, f.type, f.default)
# x <class 'float'> MISSING
# y <class 'float'> MISSING
# label <class 'str'> 

Практический пример: система конфигурации

Соберём реальный пример — конфигурацию приложения с вложенными dataclasses:

from dataclasses import dataclass, field, asdict
from typing import List, Optional
import json

@dataclass
class DatabaseConfig:
    host: str
    port: int = 5432
    name: str = "app_db"
    pool_size: int = 10

@dataclass
class CacheConfig:
    backend: str = "redis"
    host: str = "localhost"
    port: int = 6379
    ttl: int = 3600

@dataclass
class AppConfig:
    app_name: str
    environment: str
    database: DatabaseConfig
    cache: CacheConfig = field(default_factory=CacheConfig)
    allowed_hosts: List[str] = field(default_factory=list)
    debug: bool = False
    secret_key: str = field(default="", repr=False)

    def __post_init__(self):
        if self.environment == "production" and self.debug:
            raise ValueError("Debug не может быть включён в production")
        if not self.allowed_hosts:
            self.allowed_hosts = ["localhost"]

    def to_json(self) -> str:
        return json.dumps(asdict(self), indent=2, ensure_ascii=False)


config = AppConfig(
    app_name="MyService",
    environment="development",
    database=DatabaseConfig(host="db.local", name="myservice_dev"),
    secret_key="super-secret",
)

print(config.to_json())

Dataclasses и протоколы: slots и kw_only (Python 3.10+)

С Python 3.10 появились два полезных параметра.

slots=True автоматически добавляет __slots__, что снижает потребление памяти и ускоряет доступ к атрибутам:

@dataclass(slots=True)
class Vector:
    x: float
    y: float
    z: float

kw_only=True требует передавать все поля только по имени, исключая позиционные аргументы:

@dataclass(kw_only=True)
class Request:
    method: str
    path: str
    body: Optional[str] = None

# req = Request("GET", "/api")  # TypeError
req = Request(method="GET", path="/api")  # OK

Можно применять kw_only к отдельным полям через field(kw_only=True), что решает проблему с наследованием:

@dataclass
class Base:
    x: int

@dataclass
class Child(Base):
    y: int
    z: int = field(default=0, kw_only=True)

Сравнение с альтернативами

Dataclasses — не единственный инструмент для классов данных в Python.

NamedTuple из модуля typing создаёт неизменяемые объекты с доступом по индексу, но без возможности добавлять методы, изменять поля или наследовать:

from typing import NamedTuple

class Point(NamedTuple):
    x: float
    y: float

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

attrs — более мощная библиотека для тех же задач, тоже сторонняя. Dataclasses были вдохновлены attrs и покрывают большинство повседневных сценариев.

Итог

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

Ключевые сценарии применения:

  • передача данных между слоями приложения (DTO)
  • конфигурационные объекты
  • результаты запросов к базе данных или внешнему API
  • неизменяемые value-объекты с frozen=True

Для углублённого изучения Python и объектно-ориентированного программирования посмотрите курс на PurpleSchool: https://purpleschool.ru/course/python?utm_source=knowledgebase&utm_medium=text&utm_campaign=python-dataclasses

Стрелочка влевоProtocol в Python: структурная типизация и утиная типизация

Постройте личный план изучения Python до уровня Middle — бесплатно!

Python — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Python

Как отправлять запросы с помощью requests в PythonПочему Python выводит значение без команды printКак работает команда print в PythonВозможности Python для автоматизации задачРабота с JSON в Python на примерахPython get — методы получения данныхКак находить и исправлять ошибки в PythonРабота с данными через API и внешние сервисыСтруктура и оформление кода PythonОсновы Django с PythonПолезные приёмы в Python для повседневной работыИспользование locals в Python для отладкиИнтеграция PHP и PythonКак выполнять HTTPS-запросы в PythonFastAPI Python — быстрый старт: создание REST API с нуляКак работать с API в Python
Ввод целого числа в PythonВедение логов в PythonУдаление данных в Python с помощью removeОбработка исключений с помощью try/except в PythonФункция super() в Python — как вызвать метод родителяСоздание собственных контекстных менеджеров в PythonРабота с символами программирования PythonРабота с переменной X в PythonРабота с классами в PythonКак скачать Python на компьютерПростая программа на Python для начинающихОсновы Python для тех, кто начинаетЧто нового в Python 3Поддерживается ли Python 2 и стоит ли его использоватьPython 1 — с чего начиналась история языкаКоманда python print - полное руководство по выводу данныхПравила именования переменных в PythonПользовательские исключения в PythonОсновы Python coreОписание объектов PythonНаследование классов в Python — основы и примерыМножественное наследование в Python — примеры и MROКонтекстный менеджер with в Python — как работает и зачем нуженКомментарии в Python — однострочные, многострочные и docstringКакой Python выбрать для установкиКак вывести целое число с помощью print в PythonКак установить Python на Windows macOS и LinuxКак пользоваться консолью PythonКак получить последний элемент в PythonКак найти значение в PythonКак настроить PythonКак использовать print для строк в PythonКак работает интерпретатор PythonИнструкция по работе с PythonБлок finally в обработке исключений PythonЦелые числа в PythonАбстрактные классы в Python — ABC и abstractmethod
Pydantic: валидация данных в Python
Загрузка данных PythonСоздание веб-приложений на Flask PythonУправление проектами на GitHub с PythonСоздание бота на PythonСоздание интерфейсов Python QTСоздание игр с PygameСоздание GUI в PythonКак работать со словарями в PythonРабота с библиотеками через Python PackagingРабота со временем в Python при помощи модуля timePython name — особенности переменнойМатематические операции в Python с модулем mathPython listing — что это и как использоватьPytest — тестирование на Python: полное руководствоОбработка изображений с OpenCV PythonNumPy в Python — основы и применение в задачахМашинное обучение с PythonИспользование Anaconda с PythonМодуль contextlib в Python — утилиты для контекстных менеджеровБиблиотеки Python и их применение в проектах
Возврат значений из функции в PythonВложенные функции в PythonСоздание собственных декораторов в PythonЦикл while в Python и примеры использованияОбработка чисел, введённых через input в PythonРабота с функцией map в PythonОсновные операторы в Python с примерамиУсловные выражения if else в Python для начинающихКак выполняется вызов функций call в PythonПродвинутые генераторы в Python — send, throw, close и корутиныПозиционные и именованные аргументы в PythonОбъявление переменных и управление областью видимости в PythonПередача аргументов по ссылке и по значению в PythonПередача аргументов через args и kwargs в PythonОсновные методы Python и примеры их использованияОператор match/case в Python 3.10+ — основы структурного сопоставленияПаттерны match/case в Python — деструктуризация, guard и вложенные шаблоныПрактические примеры match/case в Python — реальные сценарии примененияЛокальные и глобальные переменные в PythonЧасто используемые команды PythonКлючевые слова global и nonlocal в PythonКак создавать функции в PythonКак работает сборщик мусора в PythonКак работает область видимости переменных в PythonКак работает функция callable в PythonКак работает функция any и all в PythonКак проверить тип переменной в PythonКак передать функцию как аргумент в PythonКак использовать функцию isinstance в PythonКак использовать функцию filter в PythonКак использовать функцию filter в PythonКак использовать функцию eval безопасно в PythonКак использовать декораторы в PythonИзменяемые и неизменяемые типы данных в PythonГенераторы и yield в Python — как создавать и использоватьГенераторные выражения в Python — синтаксис и примерыФункции в Python и способы их вызоваФункции как объекты в PythonЧто такое замыкания в PythonЧто делает функция reduce в PythonЧто делает функция id в PythonАргументы по умолчанию в PythonАнонимные функции и lambda в PythonАлгоритмы на Python — примеры и объяснение
Запись данных в PythonУстановка pip в PythonУправление зависимостями requirement в PythonУправление библиотеками с помощью Python PackagingУдаление пробелов с помощью strip в PythonСтруктурирование кода в PythonСоздание исполняемого файла Python в exeРазбор traceback в модуле PythonРазбор site-packages в PythonРазбор Program Files в PythonРабота с Unicode кодировками в PythonРабота с системными функциями Python sysРабота с папкой AppData в PythonРабота с модулем logging в PythonРабота с каталогами в PythonРабота с CSV в PythonВиртуальная среда venv в Python — создание и настройкаКак создать простое приложение на PythonМодули в Python и организация кода в проектеИспользование pip в Python для установки пакетовИмпорт модулей в Python и правила подключенияРабота с файлами в Python пошаговоЧто делает компилятор Python и как он работаетПолучение строки из модуля PythonПодключение файлов в Python с includeПеременные среды в PythonСборка проекта с помощью packaging в PythonНастройка Python сервераИспользование Python на UbuntuИспользование консоли PythonИспользование кодировок в PythonИнициализация пакетов PythonИмпорт модулей PythonИмпорт имен в PythonСреда IDLE Python и базовые возможностиЧтение и запись TXT в PythonЧтение файлов в Python с помощью open file
Удаление элементов из списка PythonТипы данных в Python — обзор и рекомендацииОсновные операции со строками в PythonМетоды str в Python и обработка текстаСписки в Python и их ключевые методыСоздание списков данных в PythonРабота со строками и символами в PythonРабота со столбцами в PythonРабота со списком значений в PythonРабота с таблицами в Python с помощью DataFrameРабота с RFR в PythonРабота с пробелами в PythonРабота с массивами в PythonРабота с кортежами tuple PythonРабота с координатами X и Y в PythonРабота с ключами в PythonРабота с элементами данных PythonРабота с двоичными числами PythonРабота с данными в PythonРабота с данными NumPy PythonРабота с большими числами в PythonРабота с битами в PythonРабота с байтами в PythonЧто такое значение в Python и как его определитьМножества в Python и операции с нимиИспользование range в Python для цикловПроверка на четность в PythonПроверка числа в PythonПреобразование типов в PythonПреобразование списка в строку PythonПреобразование числа в строку в PythonПостроение графиков в PythonОпределение индекса элемента в PythonОкругление чисел в PythonОбъединение списков в Python с помощью zipМножества в PythonМассивы в Python и отличие от списковМассив чисел в PythonКортежи данных в PythonКак вычислить сумму чисел в PythonКак получить остаток от деления в PythonКак найти следующее число в PythonИспользование Unicode в PythonТип int в Python и его особенностиИндекс списка в PythonФункции для работы со строками в PythonЭлементы Python и способы доступа к нимДоступ к элементам массива в PythonДеление чисел в PythonРабота с данными в Python на практикеКак работать с числами в Python
Открыть базу знаний

Лучшие курсы по теме

Иконка молнииНовый
изображение курса

Основы Python

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий