Числа и другие типы¶
У пользователя уже есть имя. Добавим идентификатор:
from bytespec import ProtoModel, field
from bytespec.types import UInt32
class User(ProtoModel):
id: UInt32
name: str
active: bool = field(default=True)
user = User(id=42, name="Anna")
decoded = User.decode(user.encode())
print(decoded.id) # 42
UInt32 означает целое без знака размером 32 бита — 4 байта.
Значение 42 остаётся обычным Python int; аннотация выбирает способ
записи. Обычный int использует знаковый VarInt (ZigZag), а фиксированный
размер выбирается явно. Для float нужно указать Float32 или Float64.
Как выбрать целое число¶
Аннотация |
Размер |
Значения |
|---|---|---|
|
1, 2, 4, 8 байт |
От |
|
1, 2, 4, 8 байт |
От |
|
1–10 байт |
Диапазон |
|
1–10 байт |
Диапазон |
Например, UInt8 подходит для чисел от 0 до 255. Значение вне диапазона
будет отклонено при encode(), а не при создании экземпляра.
Все эти аннотации импортируются из bytespec.types.
Малые числа переменной длины¶
int и VarInt используют одинаковый формат; VarUInt подходит для
неотрицательных значений. Посмотрим на байты полей:
from bytespec.types import VarUInt
class Counters(ProtoModel):
delta: int
total: VarUInt
counters = Counters(delta=-2, total=300)
assert Counters.decode(counters.encode()) == counters
print(counters.encode()[14:].hex(" "))
03 ac 02
03 — ZigZag-представление -2, ac 02 — unsigned varint 300.
Срез [14:] пропускает стандартный header, как в первой модели.
Все varints ограничены 64-битными диапазонами, даже для обычного Python int.
Числа с дробной частью¶
from bytespec.types import Float32
class Point(ProtoModel):
x: Float32
y: Float32
point = Point.decode(Point(x=1.25, y=3.5).encode())
print(point.x, point.y) # 1.25 3.5
Float32 занимает 4 байта, Float64 — 8. При записи в Float32
Python float округляется до 32-битного представления, поэтому для
сравнения произвольных дробных значений используйте допуск.
Строки и байты¶
str и bool уже знакомы из первой модели. Для бинарных данных
есть bytes:
class Packet(ProtoModel):
data: bytes
packet = Packet.decode(Packet(data=b"ABC").encode())
print(packet.data) # b'ABC'
По умолчанию строки используют UTF-8. Длина строк и bytes записывается
автоматически; настраивать её для обычного использования не нужно.
Позже изменим эти параметры в Строки, длины и настройки полей.
UUID и дата¶
Типы стандартной библиотеки можно использовать прямо в аннотациях:
from datetime import datetime, timezone
from uuid import UUID
class Message(ProtoModel):
id: UUID
created_at: datetime
message = Message(
id=UUID("12345678-1234-5678-1234-567812345678"),
created_at=datetime(2026, 9, 7, 12, 0, tzinfo=timezone.utc),
)
decoded = Message.decode(message.encode())
print(decoded.id) # 12345678-1234-5678-1234-567812345678
print(decoded.created_at.isoformat()) # 2026-09-07T12:00:00+00:00
UUID занимает ровно 16 байт. Дата записывается строкой ISO, сохраняя
представленное в ней смещение UTC. Имя часовой зоны не передаётся;
дата без tzinfo после чтения тоже остаётся без него.
Отдельные datetime.date и datetime.time не поддерживаются
автоматически: используйте свой codec, если формат требует именно их.
DatetimeCodec восстанавливает datetime, а не эти типы.
Набор именованных значений¶
Для статуса сообщения подходит строковый enum:
from enum import Enum
class Status(str, Enum):
READY = "ready"
BUSY = "busy"
class Message(ProtoModel):
status: Status
message = Message(status=Status.READY)
decoded = Message.decode(message.encode())
print(decoded.status.value) # ready
Записывается строковое значение "ready", а при чтении восстанавливается
Status.READY. Неизвестное значение вызывает DecodeError.
Числовой IntEnum тоже поддерживается автоматически:
from enum import IntEnum
class Command(IntEnum):
READ = 1
WRITE = 2
class Request(ProtoModel):
command: Command
request = Request(command=Command.WRITE)
restored = Request.decode(request.encode())
assert restored.command is Command.WRITE
print(request.encode()[14:].hex(" "))
04
По умолчанию числовое .value кодируется через VarInt, поэтому
значение 2 занимает байт 04, а не 02. При чтении восстанавливается
член enum. Для фиксированного unsigned byte используйте
EnumCodec(Command, UInt8Codec()) — Составить codec из готовых частей.
Обычный Enum без наследования от str или int требует явного codec.
Мы разобрали отдельные значения. Далее — Необязательные поля: как выразить, что значения может не быть. Полная таблица поддерживаемых аннотаций доступна в Поддерживаемые аннотации.