Python调用序列化数据工具Protocol Buffers——protobuf

it2024-11-03  4

文章目录

简介问题描述安装初试定义消息格式编译 Protocol BuffersProtocol Buffer API 拓展Protocol Buffer高级用法参考文献

本文代码

简介

Protocol Buffers是Google用于序列化结构化数据的工具,具有语言无关、平台无关、可扩展的特点。类似XML,但更小、更快、更简单。一旦定义数据的结构化方式,就可以使用特殊生成的源码从各种数据流和使用各种语言编写和读取结构化数据。

Protobuf(Google Protocol Buffers)是Google开发的的一套用于数据存储,网络通信时用于协议编解码的工具库。

它和XML、JSON差不多,将数据以某种形式保存。

Protobuf与XML、JSON的不同之处在于,它是一种二进制的数据格式,具有更高的传输,打包和解包效率。

问题描述

地址簿程序:

从文件中读写人们的联系方式每个人都有一个姓名、ID、Email和电话号码

示意图

如何序列化数据?

Python pickle。内置于Python,模式演变不好,并且难以在C++、Java应用中共享数据。自创编码。简单灵活,需要编写一次性的编解码功能,而且解析会带来少量运行时开销。适用于编码简单的数据。序列化为XML。XML人类可读,有针对的库,在需要与其他应用程序共享数据时适用。但XML过于空间密集,在编解码时会造成巨大性能损失。而且,导航XML树比导航类中的简单字段复杂得多。

Protocol Buffer可以灵活、高效、自动地解决这个问题:

编写一个.proto文件,描述存储的数据结构。Protocol Buffer编译器创建一个类,实现对数据的自动编码和解析(二进制格式)。生成的类为对应字段提供读写功能,getter和setterProtocol Buffer支持扩展数据格式,且兼容旧格式

安装

pip install protobuf

Protocol Buffers Releases 下载 win64.zip,解压到一个地方后,将 bin 目录添加到环境变量中

测试

protoc --version

初试

本节完成:

.proto文件定义消息格式使用Protocol Buffer编译器使用Python Protocol Buffer API读写消息

定义消息格式

addressbook.proto

syntax = "proto2"; package tutorial; // 包定义,防止命名冲突(在Python中没有影响) message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phones = 4; } message AddressBook { repeated Person people = 1; }

消息基础类型有:bool, int32, float, double, string

可自定义消息类型,如Person消息包含PhoneNumber消息,AddressBook消息包含Person消息

若某个字段有预定义的值,还可以定义枚举类型,如MOBILE、HOME或WORK的号码

编号1-15少一个字节,因此建议常用或重复的元素使用这些标记,而不常用的使用编号16及以上。重复字段中的每个元素都需要重新编号,因此重复字段特别适合这种优化

每个字段必须用下列修饰符进行注释:

required:必需。 如果没有设置将认为该消息未初始化,序列化未初始化的消息将引发异常。optional:可选。 如果没有设置,则使用默认值。 基础类型可以指定默认值,否则使用系统默认值(数值类型为0,字符串为空,布尔值为false)。 嵌入消息,默认值始终是消息的默认实例。repeated:可重复(包括零)。 重复值将顺序保存在Protocol Buffers中,可看作动态大小的数组。

required是永久的。 应谨慎将字段标记为required。如果需要停止必需字段,那么将该字段改为optional会有问题。旧程序认为没有该字段是不完整的。应该考虑为缓冲区编写特定于应用程序的自定义验证例程。部分Google工程师认为required弊大于利,见仁见智。

编译 Protocol Buffers

运行命令 protoc addressbook.proto --python_out=.

生成文件 addressbook_pb2.py

Protocol Buffer API

直接使用Person类

import addressbook_pb2 person = addressbook_pb2.Person() person.id = 1000 person.name = "a" person.email = "a@example.com" phone1 = person.phones.add() phone1.number = "13000000000" phone1.type = addressbook_pb2.Person.MOBILE phone = person.phones.add() phone.number = "010-60000000" phone.type = addressbook_pb2.Person.HOME print(person) # name: "a" # id: 1000 # email: "a@example.com" # phones { # number: "13000000000" # type: MOBILE # } # phones { # number: "010-60000000" # type: HOME # }

使用未定义字段将引起 AttributeError,使用不对应的数据类型将引起 TypeError

import addressbook_pb2 person = addressbook_pb2.Person() person.no_such_field = 1 # raise AttributeError person.id = "1000" # raise TypeError

标准消息方法:

IsInitialized():检查是否设置了所有的必需字段__str__():返回人类可读的消息表示CopyFrom(other_msg):用给定消息的值重写消息Clear():将所有元素清空SerializeToString():序列化消息并返回二进制字符串ParseFromString(data):解析给定字符串中的消息 import addressbook_pb2 # 1.`IsInitialized()`:检查是否设置了所有的必需字段 address_book = addressbook_pb2.AddressBook() # 实例化地址簿 person = address_book.people.add() # 实例化人 person.id = 1000 # 必需 print(person.IsInitialized()) # False person.name = "a" # 必需 print(person.IsInitialized()) # True # 2.`__str__()`:返回人类可读的消息表示 print(person) # name: "a" # id: 1000 # 3.`CopyFrom(other_msg)`:用给定消息的值重写消息 new_person = address_book.people.add() new_person.id = 1001 new_person.name = "b" person.CopyFrom(new_person) print(person) # name: "b" # id: 1001 # 4.`Clear()`:将所有元素清空 new_person.Clear() # 清空 print('已清空', new_person) # 已清空 # 5.`SerializeToString()`:序列化消息并返回二进制字符串 s = person.SerializeToString() print(type(s), s) # <class 'bytes'> b'\n\x01b\x10\xe9\x07' # 6.`ParseFromString(data)`:解析给定字符串中的消息 print(person.ParseFromString(s)) # 解析消息并更新该实例 # name: "b" # id: 1001

Protocol Buffers和面向对象设计 Protocol Buffers类基本上是哑数据(如C的结构体)。如果希望向生成的类添加更丰富的行为,最好的方法是将生成的类包装在特定应用程序的类中。

拓展Protocol Buffer

如果希望新Protocol Buffer向后兼容,旧Protocol Buffer向前兼容,那么需要遵循规则。在新版本的Protocol Buffer中:

不更改现有字段的编号不添加或删除任何必需字段可删除可选或重复字段可添加新的可选或重复字段,但必须使用全新的编号

高级用法

协议消息类提供的一个关键特性是反射。

可以遍历消息的字段并操作它们的值,而无需针对任何特定的消息类型编写代码。

使用反射的一种非常有用的方法是将协议消息与其他编码(如XML或JSON)进行转换。

反射的一种更高级的用法可能是查找相同类型的两个消息之间的差异,或者开发一种“协议消息的正则表达式”,在其中可以编写匹配特定消息内容的表达式。

更多查阅Protocol Buffers Python API

参考文献

Protocol BuffersProtocol Buffers - GitHubProtocol Buffer Basics - PythonProtocol Buffers .proto语法Protocol Buffers Python API
最新回复(0)