Необязательные поля¶
Email может быть неизвестен. Опишем это через str | None:
from bytespec import ProtoModel, field
class User(ProtoModel):
name: str
email: str | None = field(flag=0)
user = User(name="Anna")
decoded = User.decode(user.encode())
print(decoded.email) # None
str | None разрешает строку или отсутствие значения. Аргумент email
теперь можно пропустить; без default он получит None.
Optional[str] из typing означает то же самое.
flag=0 назначает полю бит присутствия в сообщении. Библиотека сама
устанавливает его, когда значение передано, и сбрасывает для None.
Читателю не нужно вычислять или передавать flags вручную.
Передать значение¶
user = User(name="Anna", email="anna@example.com")
decoded = User.decode(user.encode())
print(decoded.email) # anna@example.com
При None байты поля вообще не записываются. Пустая строка, напротив,
остаётся присутствующим значением:
user = User(name="Anna", email="")
decoded = User.decode(user.encode())
print(repr(decoded.email)) # ''
Так же различаются None и числовой ноль или False. Только None
означает отсутствие.
Добавить ещё одно optional-поле¶
Каждому полю нужен отдельный номер бита:
class User(ProtoModel):
name: str
email: str | None = field(flag=0)
nickname: str | None = field(flag=1)
user = User(name="Anna", nickname="ann")
decoded = User.decode(user.encode())
print(decoded.email, decoded.nickname) # None ann
flag — номер бита, не битовая маска. По умолчанию доступны номера
от 0 до 63; повторять их нельзя. Optional без flag и обычное
поле с flag вызывают SchemaError при объявлении класса.
Размер bitmap задаётся элементом Flags в Header и framing. Если меняете
header, оставляйте Flags для optional-полей: его отсутствие вызывает
SchemaError при объявлении класса. Проверка учитывает и унаследованные поля.
Optional со значением по умолчанию¶
default по-прежнему действует только для пропущенного аргумента:
class Message(ProtoModel):
note: str | None = field(flag=0, default="draft")
print(Message().note) # draft
print(Message(note=None).note) # None
encoded = Message(note=None).encode()
print(Message.decode(encoded).note) # None
Явное None не заменяется на "draft". При чтении отсутствие поля
также даёт None, независимо от default. Значения по умолчанию помогают
создавать экземпляры; они не восстанавливают недостающие байты сообщения.
Далее — Списки и вложенные модели: добавим пользователю список и вложим его в другую модель.