学习使我幸福
分类
Linux
2
Golang
0
Python
4
标签
OpenSSL
1
Git
1
数据结构
1
Matplotlib
1
Numpy
1
Pandas
1
平台
关于
Protoco buffers(Python)
Python
数据结构
2025-08-30
194
Protocol buffers 简称pb,是google开发的一个可序列化、反序列化object的数据交换格式,类似于xml,但是比xml更轻、更快、更简单。而且以上的重点突出一个跨平台,和xml、json等数据序列化一样,跨平台跨语言。 目前已更新到3.x.x版本,正在使用的还有2.x.x版本。 ## 一、特点 1.使用`.proto`格式文件描述数据层级结构。 2.protobuf编译器会根据`.proto`文件的描述自动创建一个class编码转换成指定的数据结构,同时生成的类会自动提供 数据转换的getter和setter方法,完成一个protocol buffer的内容的组成或是读写。 3.protobuf格式支持格式扩展兼容,使用旧的proto协议编码仍可以读取使用了新协议编码的数据,当然更新的新协议也是可以兼容之前proto的定义。 ## 二、proto介绍 `XXX.proto`文件模板举例 ```protobuf package tutorial; 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 phone = 4; } message AddressBook { repeated Person person = 1; } ``` ### 1. 文件基础 - `.proto`文件开头声明使用的语法proto2或proto3:`syntax = "proto2";` 如果不声明默认会使用proto2语法 - `.proto`文件必须以`package xxx;`声明开头(语法声明之后位置),作为协议唯一的标识,避免不同的命名冲突 - Python本身在做关系调用的时候,是作为一种正常的索引结构调用的,也就相当于将编译后的`xxx_pb.py`结尾的文件认为是普通的python脚本使用,所以在proto中定义的package在编译过程中是没有用到package的,但是避免其他语言使用的时候出现命名冲突,最好还是在文件开头做好声明。 ### 2. message - 一个`message`相当于一个指定类型的集合,像`bool/int32/float/double/string`这些类型都可以直接使用在proto协议中的某个message里,用作指定数据类型。 - 而且一个message可以直接嵌套另一个message使用,被嵌套的message就相当于string,被认为是一种数据类型。 ### 3. message中的编号和字段 - message里标记1/2/3的数字,作用是标识每种类型元素在当前二进制编码中的专属tag,不可以重复 - 编码的时候1-15的tag数字标签为一个bytes,16及以上的tag为两个bytes,所以设计message的时候尽量优化当前message数据结构,尽量给常用的或者使用过程中repeated比较多的数据使用1-15的tag数字,后续编码转换过程中可以保持最优状态 - 每个message的字段必须要声明是`required|repeated|optional`,需要关注的是,一旦定义字段为required,后面就不能再修改,否则可能引起很多问题: * 新编辑的proto内容,不可以修改已存在的filed的tag值 * 不能添加和删除任何required的filed * 新添加的filed要使用新的tag * 遵循以上原则才可以做到新旧版本message相互解析 - required字段:一定要提供一个值给该字段,否则这条message会被认为“没有初始化”。序列化没有初始化的message会出现异常。解析一条没有初始化的message会失败。之外,这个required字段的行为更类似一个optional字段。 - optional字段:该字段可以设置也可以不设置。如果一个可选字段没有设置值,会用缺省的值。系统对缺省的值定义是为0给整数类型,空串给字符串类型,False给布尔类型。optional类型的filed最好在tag编号后面和`;`前面给一个默认值`[default = value]`,当然可以不指定,默认为空。 - repeated字段:该字段会重复几次一些号码(包括0)。重复的值按顺序保存在protocl buffer中。重复的字段会被认为是动态的数组。 PS:required is forever,你应该非常小心地字段标记为required。如果在某一刻你希望停止写或发送一个必填字段,那就把不确定的字段更改为一个可选的字段---旧的阅读器会认为没有这个字段message是不完整的,而且可能会无意中拒绝或删除它们。你应该考虑为你的buffer编写特定于应用程序的自定义验证流程。某些来自Google的结论:使用required弊大于利;他们更愿意只用optional和repeated。 ### 4. message方法 message类含有一些检查或操作整个message的方式,如下: - `Isinitialized()`:检查是否所有required域都已赋值。 - `str()`:返回message的可读形式,可以通过str(message)或者print message触发,用于调试代码。 - `Clear()`:将所有域的赋值清空。 - `MergeFrom(other_msg)`:将给定的other_msg的内容合并到当前message,独立的域使用other_msg的值覆盖写入,repeated域的内容append到当前message的对应字段。独立的子message和group被递归的合并。 - `CopyFrom(other_msg)`:先对于message调用Clear()方法,再调用MergeFrom(other_msg)。 - `MergeFromString(serialized)`:将PB二进制字符解析后合并到本message,合并规则与`MergeFrom`方法一致。 - `ListFields()`:以(google.protobuf.descriptor.FieldDescriptor,value)的列表形式返回非空的域,独立的域如果HasField返回True则是非空的,repeated域至少包含一个元素则是非空的。 - `ClearField(field_name)`:清空某个域,如果被清空的域名不存在,抛出ValueError异常。 - `ByteSize()`:返回message占用的空间大小。 - `WichOneof(oneof_group)`:返回oneof组中被设置的域的名字或None,如果提供的oneof的组名不存在,抛出ValueError异常。 ### 5. Enums Enums是由metaclass扩展成一组具有符号常量的整数值。 ### 6. 序列化和解析 每个message类都有序列化和解析方法: - `SerializeToString()`:将message序列化并返回str类型的结果。(str类型只是二进制数据的一个容器而已,不是文本内容)。如果message没有初始化,跑出message.EncodeError异常。 - `SerializePartialToString()`:将message序列化并返回str类型的结果,但不检查message是否初始化。 - `ParseFromString(data)`:从给定的二进制str解析得到message对象。 PS:如果要在生成的PB类的基础上增加新功能,应采用包装(wrapper)的方式,永远不要将PB类作为基类派生子类添加新功能。 ## 三、安装 ```shell # ===Linux 系统=== # 1.进入官网,下载对应系统的压缩包 # 2.解压包到自定义目录: tar -zxvf protobuf-3.12.0.tar.gz # 3.进入protobuf文件夹,进行配置,并make,命令如下: cd protobuf-3.12.0 ./configure make && make install # 4.make成功后再进行安装,进入刚解压的protobuf下的python目录进行安装(若安装VirtualEnv注意切换Python环境) cd python python setup.py build python setup.py install python setup.py test # 5.完成 ``` ```python # ===Windows 系统=== '''注意:系统上要先装好python windows系统需下载两个包,分别为: (1) potoc-3.12.0-win64.zip(包含编译工具protoc.exe,protoc.exe需放在C:\protobuf-python-3.12.0\protobuf-3.12.0\src 和 C:\Windows\System32下,据说这样能包治百bug) (2) protobuf-python-3.12.0.zip(包含protobuf与语言python之间的protobuf运行时的库,转换时需要,相当于protobuf与各语言之间的协定格式。解压放在C盘根目录下) ''' # 1.进入官网,下载对应系统的两个压缩包(参考上面的说明) # 2.解压,根据路径配置好文件 protobuf-python-3.12.0.zip 解压---> C:\ potoc-3.12.0-win64.zip 解压---> 任意 复制potoc-3.12.0-win64.zip解压出来的protoc.exe 到 C:\protobuf-python-3.12.0\protobuf-3.12.0\src 和 C:\Windows\System32下 # 3.打开cmd开始安装,cmd切换到C:\protobuf-python-3.12.0\protobuf-3.12.0\Python目录下,依次执行下列命令 python setup.py build python setup.py test python setup.py install # 4.完成 ``` ## 四、使用 接下来我们通过一个大栗子来演示一遍上面枯燥的文字。还是文章开头的模板。 ### 1. 创建一个`addressbook.proto`文件 ```protobuf // proto2的语法声明,可加可不加,目前已到proto3版本 syntax = "proto2"; // 包名称 package tutorial; // 定义message字段类型 message Person { // 基本信息 required string name = 1; required int32 id = 2; optional string email = 3; // 枚举类型 enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } // 定义嵌套的message message PhoneNumber { required string number = 1; optional PhoneType type =2[default = HOME]; } //多笔PhoneNumber repeated PhoneNumber phones = 4; } message AddressBook { // 多笔Person repeated Person people = 1; } ``` ### 2. 编译`addressbook.proto` 写好`.proto`文件后下来就可以进行编译,以便在python上使用。 Windows是cmd命令行,linux和mac直接用命令行输入,编译完成后会生成一个叫addressbook.bp2.py的文件。 命令如下: ```shell protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto # -I :大写的I是input,意为需要编译的.proto文件路径,可以是当前路径(. 或 ./)或绝对路径 # --python_out:编译生成的xxx.bp2.py文件的保存路径。 # $SRC_DIR/addressbook.proto :最后写需要用源目标的哪个proto文件进行编译。 ``` addressbook.bp2.py文件内容比较多,我们就不展示了,可以打开进去看看。 无论生成的是java或是C++的protocol buffer代码,python protocol buffer编译器都不会生成可以直接访问数据的代码。就是你看到的addressbook_pb2.py里的内容那样。它会为你的message、枚举、字段生成指定的描述符,还有一些不好理解的空类,其中一段为: ```protobuf class Person(message.Message): __metaclass__ = reflection.GeneratedProtocolMessageType class PhoneNumber(message.Message): __metaclass__ = reflection.GeneratedProtocolMessageType DESCRIPTOR = _PERSON_PHONENUMBER DESCRIPTOR = _PERSON class AddressBook(message.Message): __metaclass__ = reflection.GeneratedProtocolMessageType DESCRIPTOR = _ADDRESSBOOK ``` 每个类中都有一些重要的语句:`__metaclass__ = reflection.GeneratedProtocolMessageType`。可以看做是创建类的模板。加载时,`GeneratedProtocolMessageType metaclass`会用python的方式指定描述符创建时需要用的message类型和添加和这些方法相关的类。然后就可以在代码中使用这些类。 ### 3. 调用 addressbook.bp2.py文件 #### 3.1 Pycharm的使用方法之一: 把addressbook.bp2.py文件直接复制或鼠标拖到pycharm的当下project目录下,然后鼠标放在pycharm当前project目录上右键依次:make_directory as ---> sources path ,将当前工作的文件夹加入source_path,就可以使用了。 #### 3.2 数据的序列化与解析 1) 调用Protocol buffer序列化数据 ```python #!/usr/bin/env python3 # coding:utf-8 import addressbook_pb2 import sys # 定义 Protocol Buffer 文件 my_pb_file = "my_addr_book.pb" # 创建 Addressbook address_book = addressbook_pb2.AddressBook() # 增加一条person信息 person = address_book.people.add() # 设定person基本信息 person.id = 123 person.name = "G.T.Wang" person.email = "GTWANG@gamil.com" # 增加第一个类型的电话 phone_number = person.phones.add() phone_number.number = "0912=345678" phone_number.type = addressbook_pb2.Person.MOBILE # 增加第二个类型的电话 phone_number = person.phones.add() phone_number.number = "06-1234567" phone_number.type = addressbook_pb2.Person.WORK # 写入AddressBook with open(my_pb_file,'wb') as f: f.write(address_book.SerializeToString()) ``` 注意:通过protocol buffer生成的是二进制文档,所以上面例子最后写入文件的时候要用bytes类型。 2)读取和解析已序列化的数据 ```python import addressbook_pb2 import sys # 指定 Protocol Buffer 文件 my_pb_file = "my_addr_book.pb" # 建立 AddressBook address_book = addressbook_pb2.AddressBook() # 写入 AddressBook with open(my_pb_file, "rb") as f: address_book.ParseFromString(f.read()) # 打印数据 for person in address_book.people: print("Person ID:", person.id) print(" Name:", person.name) if person.HasField('email'): print(" E-mail address:", person.email) for phone_number in person.phones: if phone_number.type == addressbook_pb2.Person.MOBILE: print(" Mobile phone #:", phone_number.number) elif phone_number.type == addressbook_pb2.Person.HOME: print(" Home phone #:", phone_number.number) elif phone_number.type == addressbook_pb2.Person.WORK: print(" Work phone #:", phone_number.number) ``` ## 五、结束语 一个简单的序列化数据就完成了,仅需制作一次自定义的格式化的数据,就可以使用Protbuf编译器生成各种编程语言的源码,而且更小、更快、更简单。 这只是一个Python版本的简单的栗子,还有更多功能等着你发现哦。