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

В первой модели приходилось каждый раз передавать active. Укажем, какое значение использовать, когда аргумент пропущен:

from bytespec import ProtoModel, field

class User(ProtoModel):
    name: str
    active: bool = field(default=True)

user = User(name="Anna")
decoded = User.decode(user.encode())
print(decoded.active)  # True

field() настраивает отдельное поле. Здесь default=True позволяет не передавать active конструктору. Поле остаётся частью сообщения и записывается даже тогда, когда использовано значение по умолчанию.

Переданное значение важнее default

user = User(name="Anna", active=False)
print(User.decode(user.encode()).active)  # False

Default применяется только при пропуске аргумента. Имя name по-прежнему обязательно. Неизвестный аргумент или пропуск обязательного поля вызывают TypeError.

Для сериализуемого поля нужен именно field(default=...). Обычное присваивание active: bool = True оставляет атрибут класса за пределами сообщения — это не сокращённая запись default.

Порядок полей

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

Если нужен явный порядок, первый аргумент field() задаёт индекс:

class User(ProtoModel):
    active: bool = field(1, default=True)
    name: str = field(0)

user = User(name="Anna")
print(User.decode(user.encode()).name)  # Anna

Несмотря на расположение строк, name с индексом 0 записывается первым. Индексы должны идти от нуля без повторов и пропусков. Для обычной модели их можно не указывать; правила смешивания явных и автоматических индексов собраны в Правила объявления полей.

Когда нужны другие настройки

field() также позволяет сделать поле необязательным и изменить его бинарное представление. К этим настройкам перейдём по мере необходимости. Точная сигнатура доступна в bytespec.field().

Далее — Числа и другие типы: добавим пользователю числовой идентификатор.