Модуль 6 · Урок 24 із 58

Dataclasses і data model: value object без boilerplate

Record-like object часто потребує однакових constructor, representation і equality rules. Dataclass прибирає механічний boilerplate, але не визначає domain contract замість розробника: fields, mutability, validation, secret-safe repr і semantics equality треба обрати свідомо.

dataclass__repr____eq__value object

Безпечна практика модуля 6

Пакет містить тільки synthetic records, validated value objects, composition, Protocol, deterministic summary і локальні tests. Жодних персональних чи production-даних, secrets, мережевих викликів або зовнішніх залежностей.

Завантажити практичний пакет →

dataclass генерує механіку з annotated fields

from dataclasses import dataclass

@dataclass(frozen=True, kw_only=True)
class CaseRecord:
    case_id: str
    priority: int
    minutes: int

Decorator аналізує type annotations і може згенерувати __init__, __repr__ та __eq__. Annotations самі по собі не виконують runtime validation. kw_only=True робить call sites читабельними; frozen=True блокує звичайний assignment після creation.

__post_init__ завершує invariant validation

def __post_init__(self) -> None:
    if not self.case_id.strip():
        raise ValueError("case_id required")
    if self.priority not in {1, 2, 3}:
        raise ValueError("priority must be 1..3")
    if self.minutes < 0:
        raise ValueError("minutes must be non-negative")

Generated __init__ викликає __post_init__. Для frozen object normalization через object.__setattr__ можлива, але має бути мінімальною й documented; інакше краще normalize до constructor boundary.

default_factory ізолює mutable defaults

from dataclasses import dataclass, field

@dataclass
class Batch:
    records: list[CaseRecord] = field(default_factory=list)

default_factory створює новий list для кожного instance. Це dataclass-еквівалент створення mutable field у звичайному __init__. Shared singleton list майже ніколи не є бажаним default.

repr — diagnostic interface, не місце для secret

@dataclass
class CredentialRef:
    name: str
    token: str = field(repr=False, compare=False)

Generated repr зручний для tests і logs, але за замовчуванням показує fields. Sensitive value треба виключити або взагалі не зберігати в long-lived domain object. repr=False зменшує accidental exposure, але не шифрує memory і не замінює secret lifecycle.

Equality повинна відповідати identity concept

Dataclass equality порівнює object як ordered tuple полів і вимагає однаковий type. Для value object це часто правильно. Для entity з lifecycle два records з однаковими current fields не обов’язково є тією самою сутністю; тоді generated eq може бути misleading.

first = CaseRecord(case_id="case-01", priority=1, minutes=10)
second = CaseRecord(case_id="case-01", priority=1, minutes=10)
assert first == second

Special methods інтегрують object з мовою

class CaseQueue:
    def __init__(self, records: tuple[CaseRecord, ...]) -> None:
        self._records = records

    def __len__(self) -> int:
        return len(self._records)

    def __iter__(self):
        return iter(self._records)

len(queue) шукає __len__ на type, а iteration — __iter__. Реалізуйте лише operations, semantics яких очевидні. Несподіваний __bool__, expensive __len__ або нечесне ordering роблять API складнішим.

slots — усвідомлений trade-off, не магічна оптимізація

@dataclass(slots=True) генерує slots і може зменшити memory footprint для великої кількості small objects та заборонити випадкові new attributes. Це змінює class behavior й inheritance constraints, тому застосовуйте після вимірювання або коли fixed field set є частиною design.

Decision: dataclass підходить record/value object. Якщо object має складний lifecycle, custom creation paths і багато поведінки, звичайний class може бути яснішим.

Definition of Done

  • Dataclass використано для record-like model, а не автоматично для кожного class.
  • Annotations доповнені runtime invariants там, де input не довірений.
  • Mutable defaults створюються через default_factory.
  • repr, equality, frozen, kw_only і slots мають явне обґрунтування.
  • Special methods реалізують передбачувані та дешеві operations.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію Python.

Практична перевірка · урок 24 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. Яку механічну роботу зазвичай генерує @dataclass для annotated record fields за default settings?
2. Навіщо @dataclass(kw_only=True) корисна для CaseRecord із кількома однаковими integer fields?
3. Що дає frozen=True для dataclass instance і чого цей parameter сам по собі НЕ означає для information security?