首页 > Python资料 博客日记

protobuf学习和使用(Python)

2025-01-16 12:30:10Python资料围观12

Python资料网推荐protobuf学习和使用(Python)这篇文章给大家,欢迎收藏Python资料网享受知识的乐趣

Protobuf学习

简介

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 数据类型说明
intint32 / int64int32int64 用于表示整数,int32 默认范围更小(-2^31 到 2^31-1),int64 范围更大(-2^63 到 2^63-1)。
floatfloat用于表示单精度浮点数。
doubledouble用于表示双精度浮点数。
strstring用于表示字符串。
boolbool用于表示布尔值。
bytesbytes用于表示字节序列。

复杂数据类型映射

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

Protobuf 的 message 类型映射

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

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

proto2和proto3的区别

特性proto2proto3
语法版本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

版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐