protobuf学习和使用(Python)

Protobuf学习

文章目录

  • Protobuf学习
    • 简介
    • 安装
    • example
    • 数据类型映射关系
    • proto2和proto3的区别
    • 案例
      • example1_定义基本的消息类型
      • example2_使用 repeated 字段
      • example3_定义枚举类型
      • example4_使用oneof
      • example5_字段编号与保留

简介

Protocol Buffers (Protobuf) 是一种由 Google 开发的高效、跨平台的序列化协议。它用于将结构化数据转换为紧凑的二进制格式,适用于网络通信、存储和跨语言的数据交换。Protobuf 支持多种编程语言,并允许在不破坏兼容性的情况下扩展数据结构。

主要特点:

  • 高效:比 JSON 和 XML 更加节省空间和传输速度快。
  • 跨平台和跨语言:支持多种编程语言(如 C++, Java, Python 等)。
  • 可扩展:可以在不破坏旧版本兼容性的情况下添加新字段。

通过定义 .proto 文件来描述数据结构,然后使用 Protobuf 编译器生成代码进行序列化和反序列化。

安装

windows安装编译器

下载地址:https://github.com/protocolbuffers/protobuf/releases/download/v28.3/protobuf-28.3.zip

  • 下载需要的protobuf编译器
  • 下载完成后,解压,将\protoc-28.3-win64\bin目录添加到环境变量
  • 应用后,在命令行输入protoc --version验证是否安装成功

Python安装Protobuf库

  • pip install protobuf

example

先来一个简单的示例,了解怎么去使用它。

首先需要编写一个proto文件,如Person.proto

// 声明使用的版本信息
syntax = "proto3";

// 添加包名
package user;

// 这个定义描述了一个 Person 消息,其中包含了三种字段:name(字符串类型)、id(整数类型)、email(字符串类型)。
message Person {
    string name = 1;
    int32 age = 2;
    string email = 3;
}

编译生成python文件

# python_out用于指定生成python文件
# =.表示生成的python文件在当前目录下
# Person.proto用于编译的proto源文件
protoc --python_out=. Person.proto

创建一个Python文件,用于创建和读取Person消息

import Person_pb2

# 创建一个对象
person = Person_pb2.Person()
person.name = "study_proto"
person.age = 3
person.email = "study_proto@163.com"

# 序列化消息 SerializeToString固定用法
data = person.SerializeToString()
print(f"序列化后的消息为{data}")

# 创建一个新的对象
new_person = Person_pb2.Person()
# 反序列化消息 ParseFromString固定用法
new_person.ParseFromString(data)
print(f"Name:{new_person.name}\r\nAge:{new_person.age}\r\nEmail:{new_person.email}")

运行此python文件,输出的消息如下

序列化后的消息为b'\n\x0bstudy_proto\x10\x03\x1a\x13study_proto@163.com'
Name:study_proto
Age:3
Email:study_proto@163.com

来一个说明讲一下上边做了什么事

  • 首先我们编写了一个protobuf文件
  • 然后通过proto编译器生成出来pb2文件
  • 然后创建了一个python文件用于创建和读取Person消息
  • 在这个python文件中 ,代码第10行,是进行序列化消息,可以理解为将你能看明白的消息给压缩了,这个时候只有proto能看明白这个消息是什么;这个时候的data我们没有实际的业务,如果有实际的业务他可以通过http请求发送给服务器,或者通过文件发送给用户,保存在本地,代码16行是把这个我们看不懂的消息进行反序列化,让这个我们看不懂的消息通过反序列化能够让我们看懂。
  • protobuf是一个支持跨平台的语言,我们可以生成python的代码,也可以生成c或者c++的代码,我们在自己的代码进行序列化或者反序列化,把这个消息交给其他语言,他们也是能够直接进行处理的。

数据类型映射关系

基本数据类型映射

Python 数据类型 Protobuf 数据类型 说明
int int32 / int64 int32int64 用于表示整数,int32 默认范围更小(-2^31 到 2^31-1),int64 范围更大(-2^63 到 2^63-1)。
float float 用于表示单精度浮点数。
double double 用于表示双精度浮点数。
str string 用于表示字符串。
bool bool 用于表示布尔值。
bytes bytes 用于表示字节序列。

复杂数据类型映射

Python 数据类型 Protobuf 数据类型 说明
list (Python的列表) repeated 对应 Protobuf 中的 repeated 字段,用于表示可重复的元素。 type 是数据元素的类型。
dict (Python字典) map 用于表示键值对集合。key_typevalue_type 分别为键和值的类型。
tuple (Python元组) 无法直接映射 Python 中的元组没有直接的 Protobuf 对应类型,一般可以用 messagerepeated 来代替

Protobuf 的 message 类型映射

Protobuf 中的 message 类型通常用于表示一个复杂的数据结构,Python 中可以使用类来表示。

Python 数据类型 Protobuf 数据类型 说明
class (Python类) message Protobuf 的 message 类型。每个字段都有自己的类型,可以是基本类型,也可以是嵌套的 message 类型。

proto2和proto3的区别

特性 proto2 proto3
语法版本 syntax = "proto2"; syntax = "proto3";
字段默认值 支持字段的默认值,可以为字段设置默认值。 不支持字段的默认值,所有字段有默认值(如 0""false)。
必选字段(required) 支持 required 字段,表示该字段是必须的。 不支持 required 字段,所有字段都是可选的(optional)。
可选字段(optional) 支持 optional 字段,字段可以选择不提供。 optional 字段在 proto3 中默认为隐式的,无需显式声明。
枚举值的默认值 枚举类型支持一个默认值。 枚举的第一个值默认为默认值。
扩展(extensions) 支持扩展字段,可以在未修改原始 .proto 文件的情况下,扩展消息结构。 不支持扩展(extensions)。
Any 类型 不支持 Any 类型。 支持 Any 类型,可以包含任何类型的数据。
未知字段 不会忽略未识别的字段,通常会导致解析错误。 自动忽略未知字段,允许向现有的消息中添加字段而不影响兼容性。
字段类型命名 支持 requiredoptionalrepeated 类型字段。 只有 repeatedoptional(隐式)类型字段。
重复字段(repeated) 支持 repeated 字段,用于存储多个相同类型的值。 支持 repeated 字段,行为与 proto2 相同。
oneof 支持 oneof,用于表示多种选择中的一个字段。 支持 oneof,行为与 proto2 相同。
默认值和初始化 每个字段都有显式的默认值,用户可以在 .proto 文件中指定。 默认值隐式地由类型决定(例如,数值类型默认为 0,字符串默认为 "")。
注释 支持注释,用于代码生成时的文档。 支持注释,用于代码生成时的文档。
代码生成 生成的代码中会包含 requiredoptionaldefault 生成的代码省略 requireddefault,并且所有字段为隐式 optional
字段命名 字段名称可按要求自由命名。 字段名称也按要求自由命名,但更注重简洁性和兼容性。

案例

example1_定义基本的消息类型

  • 定义一个Person消息,包含以下字段:
    • 姓名(name,字符串类型)
    • 年龄(age,整数类型)
    • 是否订阅了新闻邮件(subscribed,布尔类型)
  • 定义一个Address消息,包含:
    • 街道(street,字符串类型)
    • 城市(city,字符串类型)
    • 邮政编码(zip_code,字符串类型)
  • Person 消息中嵌套一个 Address 字段。

Person.proto

syntax = "proto3";

package person;

message Address {
    string street = 1;
    string city = 2;
    string zip_code = 3;
}

message Person {
    string name = 1;
    int32 age = 2;
    bool subscribed = 3;
    Address address = 4;
}

根据proto文件生成编译后的python文件

protoc --python_out=. Person.proto

main.py

import Person_pb2

# 创建一个Person对象
person = Person_pb2.Person()
person.name = "jason"
person.age = 18

person.subscribed = True
person.address.street = "人民街道"
person.address.city = "上海"
person.address.zip_code = "410000"

# 序列化消息
encode_data = person.SerializeToString()
print(encode_data)

# 创建一个新对象
new_person = Person_pb2.Person()
# 反序列化消息
new_person.ParseFromString(encode_data)
print(new_person)

运行输出

b'\n\x05jason\x10\x12\x18\x01"\x1e\n\x0c\xe4\xba\xba\xe6\xb0\x91\xe8\xa1\x97\xe9\x81\x93\x12\x06\xe4\xb8\x8a\xe6\xb5\xb7\x1a\x06410000'
name: "jason"
age: 18
subscribed: true
address {
  street: "人民街道"
  city: "上海"
  zip_code: "410000"
}

example2_使用 repeated 字段

  • 使用 repeated 字段

    • 定义一个Library消息,包含:

      • 图书名称列表(book_titlesrepeated string 类型)
    • 创建一个包含多个图书名称的 Library 实例,并将其序列化和反序列化。

  • 使用 repeated 字段可以方便地存储多个相同类型的数据。
  • SerializeToString()ParseFromString() 分别用于序列化和反序列化。
  • Protobuf 在处理大规模数据交换时非常高效,特别适合跨语言的应用场景。

Library.proto

syntax = "proto3";
package library;


message Library{
    repeated string book_titles = 1;
}

通过proto文件编译生成python文件

protoc --python_out=. Library.proto

main.py

import Library_pb2

library = Library_pb2.Library()
for i in range(1, 20):
    library.book_titles.append(f"人民的名义{i}")
encode_data = library.SerializeToString()
library.ParseFromString(encode_data)
for i in library.book_titles:
    print(i)

输出

b'\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x891\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x892\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x893\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x894\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x895\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x896\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x897\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x898\n\x10\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x899\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8910\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8911\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8912\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8913\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8914\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8915\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8916\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8917\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8918\n\x11\xe4\xba\xba\xe6\xb0\x91\xe7\x9a\x84\xe5\x90\x8d\xe4\xb9\x8919'
人民的名义1
人民的名义2
人民的名义3
人民的名义4
人民的名义5
人民的名义6
人民的名义7
人民的名义8
人民的名义9
人民的名义10
人民的名义11
人民的名义12
人民的名义13
人民的名义14
人民的名义15
人民的名义16
人民的名义17
人民的名义18
人民的名义19

example3_定义枚举类型

  • 定义一个 DayOfWeek 枚举,包含七个值:MONDAYTUESDAYWEDNESDAYTHURSDAYFRIDAYSATURDAYSUNDAY
  • 创建一个Schedule消息,包含:
    • 星期几(day,枚举类型)
    • 活动描述(activity,字符串类型)
  1. 枚举定义:使用 enum 关键字定义枚举类型,每个枚举常量都被分配一个整数值。
  2. 在消息中使用枚举:在 Protobuf 消息中可以使用定义好的枚举类型字段,从而提高代码的可读性和数据结构的清晰度。
  3. Python 使用:在 Python 中,枚举的值以整数表示,使用 Name() 方法可以将整数值转换为枚举常量的名称。

enum.proto

syntax = "proto3";

package study_enum;

enum DayOfWeek {
    MONDAY = 0;
    TUESDAY = 1;
    WEDNESDAY = 2;
    THURSDAY = 3;
    FRIDAY = 4;
    SATURDAY = 5;
    SUNDAY = 6;
}

message Schedule {
    DayOfWeek day = 1;
    string activity = 2;
}

通过proto文件编译生成python文件

protoc --python_out=. enum.proto

main.py

import enum_pb2

test_enum = enum_pb2.Schedule()
test_enum.day = enum_pb2.DayOfWeek.MONDAY
test_enum.activity = "上学"
data = test_enum.SerializeToString()
print(data)
test_enum.ParseFromString(data)
print(test_enum.day)
print(enum_pb2.DayOfWeek.Name(test_enum.day))
print(test_enum.activity)

输出

b'\x12\x06\xe4\xb8\x8a\xe5\xad\xa6'
0
MONDAY
上学

example4_使用oneof

  • 定义一个ContactInfo消息,包含以下字段:
    • phone_number(字符串类型)
    • email(字符串类型)
    • 使用 oneof 来表示联系人信息字段,只能选择其中一个(电话号码或电子邮件)。
  1. 定义 oneof:使用 oneof 可以在同一消息中定义多个互斥字段,但任何时候只能设置其中一个字段。
  2. 存储空间节省:所有的 oneof 字段共享内存空间,因此只会有一个字段被存储。
  3. 互斥字段:设置一个 oneof 字段时,其他字段会被自动清除。
  4. 访问 oneof 字段:可以使用 HasField 方法检查某个字段是否已经被设置。

oneof.proto

syntax = "proto3";

package study_oneOf;

message ContactInfo {
    oneof contact {
        string phone_number = 1;
        string email = 2;
    };
}

通过proto文件编译生成python文件

protoc --python_out=. oneof.proto

main.py

import oneof_pb2

contact = oneof_pb2.ContactInfo()
contact.email = "123@qq.com"
# 使用HasField检查字段是否设置
is_phone_number = contact.HasField("phone_number")
is_email = contact.HasField("email")
print(is_phone_number, is_email)
encode_data = contact.SerializeToString()
print(encode_data)

contact.ParseFromString(encode_data)
print(contact.phone_number)
print(contact.email)

输出

False True
b'\x12\n123@qq.com'

123@qq.com

example5_字段编号与保留

  • 定义一个Person消息,包含以下字段:

    • 姓名(name,字符串类型)
    • 年龄(age,整数类型)
    • 使用 reserved 来保留字段编号 2 和 3,避免将来重复使用。
  • reserved 关键字用于保留 字段编号字段名称,防止它们在未来被重用。

  • 它对于处理字段删除、重命名以及防止版本间字段冲突非常有用。

  • reserved 语法可以指定字段编号和字段名称,避免不必要的冲突。

person.proto

syntax = "proto3";

package person;


message Person {
    string name = 1;
    int32 age = 2;
    reserved 3, 4;
}

通过proto文件编译生成python文件

protoc --python_out=. person.proto

main.py

import Person_pb2


person = Person_pb2.Person()
person.name = "zhangsan"
person.age = 18
encode_data = person.SerializeToString()
print(encode_data)

new_person = Person_pb2.Person()
new_person.ParseFromString(encode_data)
print(new_person.name)
print(new_person.age)

输出

b'\n\x08zhangsan\x10\x12'
zhangsan
18
syntax = "proto3";

package person;


message Person {
    string name = 1;
    int32 age = 2;
    reserved 3, 4;
}

通过proto文件编译生成python文件

protoc --python_out=. person.proto

main.py

import Person_pb2


person = Person_pb2.Person()
person.name = "zhangsan"
person.age = 18
encode_data = person.SerializeToString()
print(encode_data)

new_person = Person_pb2.Person()
new_person.ParseFromString(encode_data)
print(new_person.name)
print(new_person.age)

输出

b'\n\x08zhangsan\x10\x12'
zhangsan
18

你可能感兴趣的:(Python,学习,python,开发语言)