Protobuf-py: Protocol Buffers для Python без компромиссов
Компания Buf, создатели Buf CLI, Buf Schema Registry, ConnectRPC, Protovalidate и protobuf-es для TypeScript, выпустила protobuf-py: библиотеку Protocol Buffers для Python, написанную полностью с нуля. Она проходит весь набор тестов Google на соответствие спецификации (conformance suite), и по бинарному, и по JSON-формату, для proto2, proto3 и editions, без единого провала, поддерживает расширения, кастомные опции, неизвестные (unknown) поля, динамические сообщения и стандартные встроенные типы. Генерируемый код читаем и типизирован, у библиотеки нет зависимостей во время выполнения, и она работает на чистом Python 3.10+. С установленным Rust-ускорителем она показывает такую же скорость в реальной нагрузке, как upb, C-движок, на котором построен официальный пакет Google.
До сих пор у Python-разработчиков был выбор между полным, но неудобным Google-пакетом и приятной в использовании, но урезанной библиотекой betterproto (только proto3, без proto2, editions, расширений и кастомных опций). Та же проблема, грубый API поверх мощного движка, воспроизводится и в grpcio на уровне RPC. Buf изначально хотели решить именно RPC-слой в своей библиотеке connect-py (реализация ConnectRPC, совместимая и с Connect, и с gRPC), но поняли, что транспортного слоя недостаточно: под ним нужна подходящая реализация самого Protobuf, а такой основы для Python не было. Отсюда и родился protobuf-py.
Официальный пакет Google из PyPI по умолчанию использует upb, движок на языке C: сообщение живёт в общей C-области памяти (arena), а Python-объект, лишь ссылка (хендл) на неё; чтение поля означает переход в код на C и материализацию Python-объекта на обратном пути. Это оставляет конкретные проблемы: сгенерированные файлы _pb2.py почти нечитаемы, потому что классы собираются во время импорта из сериализованных байтов дескрипторов; API скроен под C++ (SerializeToString, HasField, WhichOneof, CopyFrom), например, HasField выбрасывает ошибку при вызове на proto3-скаляре, а WhichOneof возвращает строку, которую нужно вручную передавать в getattr; сгенерированные импорты абсолютные и ломаются при вложении в пакет, на PyPI даже есть отдельный инструмент fix-protobuf-imports только для того, чтобы их чинить; наконец, типы регистрируются в едином пуле на весь процесс, поэтому импорт двух разных сборок одного и того же .proto-файла роняет программу во время выполнения.
protobuf-py устроен иначе: сообщение, обычный Python-объект с slots, а его поля, обычные Python-значения (числа, строки, списки, вложенные сообщения). Rust-ускоритель разгоняет только парсинг и сериализацию, записывая результат прямо в объект, а после парсинга чтение поля, это просто обращение к атрибуту Python. Поскольку данные настоящие Python-данные, сгенерированный код тоже настоящий и читаемый: поля oneof (группа взаимоисключающих полей) можно проверять сопоставлением с образцом, и статический анализатор типов сужает каждую ветку; перечисления реализованы как настоящий IntEnum; pyright, mypy и ty понимают сгенерированный код без отдельных файлов с описанием типов; файлы используют относительные импорты и могут лежать где угодно, а типы разрешаются через явный реестр (Registry), а не общий глобальный пул. При этом библиотека покрывает спецификацию целиком, а не урезанное подмножество: proto2, proto3, editions, расширения, кастомные опции, группы (groups), сохранение неизвестных полей при повторной сериализации, упакованные и развёрнутые повторяющиеся поля и полное ProtoJSON-кодирование встроенных типов. Она проходит тот же набор тестов на соответствие спецификации, которым Google проверяет собственные реализации, без единого провала по бинарному и JSON-форматам; пустой список ошибок специально закоммичен в репозиторий, и CI (автоматическая проверка при каждом изменении) не даст ему перестать быть пустым.
Бенчмарк, который просто парсит сообщение и тут же его отбрасывает, делает upb недосягаемым, потому что этот движок откладывает работу до момента чтения поля. Реальный же production-код парсит сообщение один раз, разбирает поля по условиям, достаёт несколько значений, копирует сообщение и сериализует изменённую версию обратно. upb платит цену перевода в Python-объект при каждом таком чтении, а protobuf-py платит её ровно один раз, при первом же чтении, потому что парсинг уже создал обычный Python-объект. Поэтому protobuf-py делает больше работы заранее, но эта работа окупается, если код потом делает с сообщением достаточно много. На бенчмарке, моделирующем сборку ответа для домашней страницы соцсети, с объёмным текстовым содержимым (полные посты Reddit, многострочные био и уведомления, характерная нагрузка для соцсетей, документов и ИИ-агентов), в изолированном тесте одного лишь маршалинга (упаковки данных) upb действительно быстрее, как и ожидалось, но на сквозном сценарии, приближенном к реальному production-коду, впереди оказывается protobuf-py.
Rust-ускоритель необязателен и подключается автоматически, отдельный Rust-инструментарий не нужен ни для использования библиотеки, ни для участия в её разработке: готовые пакеты (wheels) уже содержат ускоритель, а чистый Python-путь без него ведёт себя идентично, только медленнее; библиотека работает и на многопоточной сборке Python 3.14 без GIL (глобальной блокировки интерпретатора). Чтобы попробовать protobuf-py, достаточно указать свои .proto-файлы в buf.gen.yaml и запустить генерацию, на выходе получается типизированный файл _pb.py, который можно открыть и прочитать как обычный код; документация, исходники и стенд для бенчмарков (test_bench.py) открыты на GitHub. Библиотеку выпустила Buf, компания, которая уже несколько лет делает инструменты вокруг Protobuf (Buf CLI, Buf Schema Registry, ConnectRPC, Protovalidate, protobuf-es для TypeScript), а её инженеры участвовали и в разработке самой спецификации Protobuf editions.
Ключевые факты
- Buf выпустила protobuf-py, библиотеку Protocol Buffers для Python, написанную с нуля: она проходит весь набор тестов Google на соответствие спецификации (по бинарному и JSON-форматам, для proto2, proto3 и editions) без единого провала и поддерживает расширения, кастомные опции, неизвестные поля и динамические сообщения.
- В отличие от официального Google-пакета, обёртки вокруг C-движка upb, где Python-объект, лишь ссылка на область памяти в C, сообщения protobuf-py хранятся как обычные Python-объекты с slots: сгенерированный код читаем и типизирован, а pyright, mypy и ty понимают его без отдельных файлов с описанием типов.
- Опциональный Rust-ускоритель разгоняет парсинг и сериализацию; на сквозном бенчмарке, сборка ответа для домашней страницы соцсети с объёмным текстовым содержимым, protobuf-py обгоняет upb, хотя в изолированном тесте одного лишь маршалинга (упаковки данных) upb быстрее.
- Библиотека устраняет конкретные проблемы Google-пакета: метод HasField выбрасывает ошибку на proto3-скалярах, абсолютные импорты ломаются при вложении в пакет (для починки на PyPI существует отдельный инструмент fix-protobuf-imports), а типы регистрируются в едином пуле на весь процесс, из-за чего импорт двух сборок одного .proto роняет программу.
- У библиотеки нет зависимостей во время выполнения, она работает на чистом Python 3.10+ и на многопоточной сборке Python 3.14 без GIL; исходники, документация и стенд для бенчмарков (test_bench.py) открыты на GitHub.
Почему это важно
До protobuf-py у Python-разработчиков был выбор между полным, но неудобным пакетом Google (обёрткой вокруг C-движка upb с API, скроенным под C++ и Java) и приятной в использовании, но урезанной betterproto (только proto3, без proto2, editions, расширений и кастомных опций). Та же проблема воспроизводится и в grpcio на уровне RPC. Buf изначально пытались решить именно RPC-слой в connect-py, своей реализации ConnectRPC, которая говорит и на Connect, и на gRPC, но поняли, что транспортного слоя мало: под ним нужна подходящая реализация самого Protobuf, а такой основы для Python не было. protobuf-py, попытка одновременно дать полное покрытие спецификации, читаемый код и производительность, не жертвуя ни одним из трёх.
Кому это важно
Библиотека нацелена на Python-разработчиков, у которых Protobuf уже используется в конвейерах данных, ML-системах, ИИ-агентах, инфраструктурных скриптах, RPC-сервисах и инструментах разработки. Особенно она важна для тех, кто полагается на статическую типизацию (pyright, mypy, ty) и хочет получать сгенерированный код без отдельных файлов с описанием типов, а также для пользователей connect-py, собственной реализации ConnectRPC от Buf, для которой protobuf-py и задумывался как основа.
Как это применить
Чтобы попробовать библиотеку, нужно указать свои .proto-файлы в buf.gen.yaml и запустить генерацию, на выходе получится типизированный, читаемый файл _pb.py, который можно открыть и понять без обращения к документации. Библиотека не имеет зависимостей во время выполнения и работает на чистом Python 3.10+, включая многопоточную сборку Python 3.14 без GIL (глобальной блокировки интерпретатора). Rust-ускоритель для парсинга и сериализации подключается автоматически через готовые пакеты (wheels) и не требует отдельного Rust-инструментария ни для использования, ни для разработки, без него чистый Python-путь ведёт себя так же, но медленнее. Исходный код, документация и стенд для бенчмарков (test_bench.py) открыты на GitHub.
Можно ли доверять
Библиотеку выпустила Buf, компания с многолетним опытом в экосистеме Protobuf: Buf CLI, Buf Schema Registry, ConnectRPC, Protovalidate и protobuf-es для TypeScript, а её инженеры участвовали в разработке самой спецификации Protobuf editions. protobuf-py проходит полный набор тестов на соответствие спецификации (conformance suite), которым Google проверяет собственные реализации, без единого провала по бинарному и JSON-форматам для proto2, proto3 и editions; пустой список ошибок специально закоммичен в репозиторий, и CI не даст ему перестать быть пустым. При этом это анонс только что выпущенной библиотеки: на Hacker News обсуждение за 102 часа набрало всего 36 баллов и 3 комментария, независимой практики использования за пределами самой Buf пока не накопилось.
Риски и подводные камни
Все цифры производительности в анонсе приведены самой Buf, независимой проверки со стороны пока не было, хотя стенд для бенчмарков (test_bench.py) публично доступен и его можно перепроверить самостоятельно. Библиотека совсем новая: миграция с Google-пакета, betterproto или grpcio на protobuf-py в тексте не описана. Полная скорость требует установленного Rust-ускорителя, на чистом Python без него, судя по описанию, поведение то же самое, но медленнее. Использование библиотеки завязано на генератор кода самой Buf (buf.gen.yaml), а значит подразумевает как минимум частичную привязку к её инструментарию.
«Python слишком важен, чтобы Protobuf оставался для него довеском.»
— Buf, анонс protobuf-py