Урок курса

Dataclass, NamedTuple или обычный класс в Python

Python для продвинутых: ООП, типизация и тестирование

@dataclass генерирует служебные методы для класса, описанного аннотациями: обычно __init__, __repr__ и __eq__. Это уменьшает шаблонный код, но оставляет полноценный класс с методами и явным поведением.

from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class Point:
    x: float
    y: float

    def distance_squared(self) -> float:
        return self.x ** 2 + self.y ** 2

frozen=True запрещает обычное изменение полей после создания, а slots=True может уменьшить память и запретить случайные новые атрибуты. Это настройки контракта, а не универсальные ускорители.

Чем отличается NamedTuple

typing.NamedTuple создаёт неизменяемый tuple-подобный объект с именованными полями. Он поддерживает распаковку и индексацию, поэтому удобен на границе со старым кодом, ожидающим кортеж. Но tuple-семантика иногда вредна: два разных типа NamedTuple с одинаковыми значениями могут сравниваться как равные последовательности.

Обычный класс лучше, когда создание объекта должно строго проверять инварианты, есть сложное наследование или публичный конструктор не совпадает с набором хранимых полей. Dataclass тоже поддерживает __post_init__, но не стоит превращать его в скрытую фабрику со множеством побочных эффектов.

Словарь остаётся подходящим для действительно динамической схемы. Если набор ключей стабилен и значим для домена, отдельный тип обычно улучшает подсказки IDE и рефакторинг.

Практический выбор

Используйте dataclass для внутренних value objects и DTO, когда поля известны, нужны понятный repr и сравнение. Используйте frozen dataclass для значений, которые не должны меняться после создания. NamedTuple уместен для компактной неизменяемой записи с необходимой совместимостью с tuple.

Аннотации dataclass сами по себе не валидируют типы во время выполнения. Передача строки в поле float не вызовет автоматическую ошибку. Для внешнего недоверенного ввода нужен отдельный валидатор или модель данных с runtime-проверкой.

Не выбирайте структуру только по числу строк кода. Важнее семантика: можно ли менять объект, должен ли он быть hashable, нужна ли tuple-совместимость и где проверяются инварианты.

Проверяем изменяемость dataclass и NamedTuple

Возьмём одинаковые поля координат и два способа описать точку. Обычный dataclass по умолчанию изменяемый. NamedTuple запрещает переназначение поля, но это не гарантирует глубокую неизменяемость вложенного списка. Поэтому сначала спросите, нужны ли объекту методы и изменение состояния, а потом выбирайте синтаксис.

from dataclasses import dataclass
from typing import NamedTuple
@dataclass
class MutablePoint:
    x: int
    y: int
class FixedPoint(NamedTuple):
    x: int
    y: int
p = MutablePoint(1,2)
p.x = 7
q = FixedPoint(1,2)
new_q = q._replace(x=7)
print(p, q, new_q)
assert p.x == new_q.x == 7 and q.x == 1

Изменение dataclass произошло на том же экземпляре, а _replace вернул новый NamedTuple. Аннотация x: int сама по себе не валидирует вход при создании этих объектов. Если от внешнего JSON требуется runtime-проверка, проектируйте её отдельно. Для неизменяемого dataclass есть frozen=True, однако вложенные изменяемые объекты требуют отдельного внимания.

Продолжите разбор: Type hints и mypy в Python: практическая типизация.

Задача 1

Что @dataclass делает с аннотациями типов по умолчанию?

Выберите один вариант
Без регистрации · результат не сохраняется

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку