Модуль 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 треба обрати свідомо.
Безпечна практика модуля 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: intDecorator аналізує 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 == secondSpecial 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.
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.
Закріпіть матеріал уроку
Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.