笔者最近在学习使用tensorflow/serving,其中有不少涉及Protobuf相关的内容,因此接触学习了Prorobuf,记录于此,希望能对读者有所启发。

  本文作为Protobuf入门学习的第一篇文章,将简单介绍Protobuf协议以及如何使用Protobuf来实现序列化与反序列化。

Protobuf简介

  Protobuf即Protocol Buffers,是Google公司开发的一种跨语言和平台的序列化数据结构的方式,是一个灵活的、高效的用于序列化数据的协议。

  与XML和JSON格式相比,Protobuf更小、更快、更便捷。Protobuf是跨语言的,并且自带一个编译器(protoc),只需要用protoc进行编译,就可以编译成Java、Python、C++、C#、Go等多种语言代码,然后可以直接使用,不需要再写其它代码,自带有解析的代码。

  只需要将要被序列化的结构化数据定义一次(在.proto文件定义),便可以使用特别生成的源代码(使用Protobuf提供的生成工具)轻松的使用不同的数据流完成对结构数据的读写操作。甚至可以更新.proto文件中对数据结构的定义而不会破坏依赖旧格式编译出来的程序。

  Protobuf的优点如下:

  • 性能号,效率高。序列化后字节占用空间比XML少3-10倍,序列化的时间效率比XML快20-100倍。
  • 有代码生成机制。将对结构化数据的操作封装成一个类,便于使用。
  • 支持向后和向前兼容。当客户端和服务器同时使用一块协议的时候, 当客户端在协议中增加一个字节,并不会影响客户端的使用。
  • 支持多种编程语言。Protobuf目前已经支持Java,C++,Python、Go、Ruby等多种语言。

  Protobuf的缺点如下:

  • 二进制格式导致可读性差
  • 缺乏自描述
  • 应用不是很广泛

  现阶段Protobuf的应用场景主要有Google的gRPC, Tensorflow/Serving等。

Protobuf安装

  这里介绍在Linux环境中如何安装Protobuf编译器,以及对应的Python模块。

  首先,下载Protobuf的源代码:

wget https://github.com/google/protobuf/releases/download/v3.6.0/protobuf-python-3.6.0.tar.gz

  解压文件,编译并安装:

tar zxvf protobuf-python-3.6.0.tar.gz
cd protobuf-3.6.0
./configure
make
make check
make install

  验证Protobuf是否安装成功:

protoc --version
libprotoc 3.6.0(笔者电脑上的输出结果)

  Protobuf对应的Python第三方模块为protobuf,直接使用pip安装即可:pip3 install protobuf,验证安装成功的办法如下:

$ python
Python 3.6.10 |Anaconda, Inc.| (default, May  8 2020, 02:54:21)
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import google.protobuf
>>>

如果导入第三方模块没有报错,则表明该模块安装成功。

.proto文件格式说明

  在前面的简介部分已经说过,Protobuf在使用时定义序列化结构的文件为后缀是.proto的文件。

  .proto文件有专门的语法结构,ProtoBuf有两个语法版本:v2与v3。message 用来定义一个数据结构。

  我们先来看一个简单的.proto文件的例子:

syntax = "proto3";message Person {int64 id = 1;string name = 2;repeated string skills = 3;  // 这里表示skills可以接受多个string类型的值
}

文件的首行生命该语法使用Protobuf3语法,同时在文件后面定义了Person消息,该消息有三个字段:id, name, skill。

  每个字段的定义格式为 指定字段规则 数据类型 变量名称=数字标识符

  指定字段规则在Protobuf3语法中只有repeated、singular两种类型,其中singular类型(默认类型,不需要声明)表示有0个或者1个这种字段(但是不能超过1个);repeated类型表示该字段可以重复任意多次(包括0次),重复值的顺序会被保留。

  数据类型常见的有double、float、int32、string、bytes、bool等,也可以是枚举、嵌套消息类型、Any、oneof等。

  在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。最小的标识号可以从1开始,最大到2^29-1(536,870,911)。不可以使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。

注:[1,15]之内的标识号在编码的时候会占用一个字节。
[16,2047]之内的标识号则占用2个字节。
所以应该为那些频繁出现的消息元素保留[1,15]之内的标识号。
切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

序列化与反序列化

  下面将通过一个简单的里来介绍如何使用Protobuf来实现序列化与反序列化。

  定义数据结构文件(person_and_book.proto)如下:

syntax = "proto3";message Book {  // 书籍信息string name = 1;float price = 2;string press = 3;repeated Person people = 4;
}message Person {  // 人物信息int32 id = 1;string name = 2;int32 age = 3;string email = 4;string job = 5;bool work_status = 6;string city = 7;MyAddress maps = 8;
}message MyAddress { // 地址信息,字段类型为mapmap<string, string> tell_address = 1;
}

  使用protoc编译person_and_book.proto文件, 命令行如下:

protoc ./person_and_book.proto  --python_out=./

编译完毕,会自动生成person_and_book_pb2.py文件。在命令行中,./person_and_book.proto为需要编译的.proto文件所在路径,python_out为输出python脚本路径,./表示为当前路径。

  接着我们使用一个新的脚本(add_person.py)针对该数据结构进行序列化与反序列化。完整代码如下:

# -*- coding: utf-8 -*-
import person_and_book_pb2# 书籍信息
book = person_and_book_pb2.Book()
book.name = "Introduction to Protobuf"
book.price = 8.5
book.press = "NY Press"# 添加人物信息
person = book.people.add()
person.id = 1
person.name = "protobuf"
person.age = 25
person.email = "protobuf@163.com"
person.job = "college professor"
person.work_status = True
person.city = "Shanghai"# 添加人物的地址信息
address_maps = person.maps
address_maps.tell_address["born place"] = "SX"
address_maps.tell_address["living place"] = "SH"
address_maps.tell_address["visited place"] = "BJ, GZ, SY"# 序列化
serializeToString = book.SerializeToString()
print(type(serializeToString), serializeToString)# 反序列化
parsed_book = person_and_book_pb2.Book()
parsed_book.ParseFromString(serializeToString)
print(type(parsed_book))# 输出书籍信息
print("book_name: %s, book_price: %s, book_press: %s" % (parsed_book.name, parsed_book.price, parsed_book.press))# 输出人物信息
for person in parsed_book.people:print("p_id: %s, p_name: %s, p_age: %s, p_email: %s, p_job: %s, p_work_status: %s, p_city: %s"% (person.id, person.name, person.age, person.email, person.job, person.work_status, person.city))for key in person.maps.tell_address:print(key, person.maps.tell_address[key])

输出结果如下:

<class 'bytes'> b'\n\x18Introduction to Protobuf\x15\x00\x00\x08A\x1a\x08NY Press"\x84\x01\x08\x01\x12\x08protobuf\x18\x19"\x10protobuf@163.com*\x11college professor0\x01:\x08ShanghaiBC\n\x1b\n\rvisited place\x12\nBJ, GZ, SY\n\x12\n\x0cliving place\x12\x02SH\n\x10\n\nborn place\x12\x02SX'
<class 'person_and_book_pb2.Book'>
book_name: Introduction to Protobuf, book_price: 8.5, book_press: NY Press
p_id: 1, p_name: protobuf, p_age: 25, p_email: protobuf@163.com, p_job: college professor, p_work_status: True, p_city: Shanghai
born place SX
living place SH
visited place BJ, GZ, SY

从文件中读取message

  google.protobuf模块中的text_format脚本允许我们直接对文本格式的消息进行解析。

  我们有如下文本格式的消息(book_example.ini):

name: "Learning Protobuf",
price: 10,
press: "SH Edu Press",
people {id: 2name: "JC",age: 28,email: "jc@qq.com",job: "IT",work_status: True,city: "SH",maps {tell_address: {key: "born place", value: "SX"},tell_address: {key: "living place", value: "HZ"},tell_address: {key: "visited place", value: "LZ, HZ, SZ, YZ, DH, JYG"},tell_address: {key: "educated place", value: "SX, HZ, SH"},}
}

  解析该文本格式消息的代码(read_message_from_text)如下:

# -*- coding: utf-8 -*-
from google.protobuf import text_format
import person_and_book_pb2# message: Book
book = person_and_book_pb2.Book()model_config_file_path = "book_example.ini"
with open(model_config_file_path, 'r+') as f:book_ini = f.read()parsed_book = text_format.Parse(text=book_ini, message=book)# 输出解析信息
# 输出书籍信息
print("book_name: %s, book_price: %s, book_press: %s" % (parsed_book.name, parsed_book.price, parsed_book.press))# 输出人物信息
for person in parsed_book.people:print("p_id: %s, p_name: %s, p_age: %s, p_email: %s, p_job: %s, p_work_status: %s, p_city: %s"% (person.id, person.name, person.age, person.email, person.job, person.work_status, person.city))for key in person.maps.tell_address:print("{}: {}".format(key, person.maps.tell_address[key]))

输出结果如下:

book_name: Learning Protobuf, book_price: 10.0, book_press: SH Edu Press
p_id: 2, p_name: JC, p_age: 28, p_email: jc@qq.com, p_job: IT, p_work_status: True, p_city: SH
visited place: LZ, HZ, SZ, YZ, DH, JYG
born place: SX
living place: HZ
educated place: SX, HZ, SH

oneof类型

  oneof类型用来代表在实现的时候,该组属性中有且只能有一个被定义,不能出现多个。

  我们有如下的数据结构(oneof_test.proto):

syntax = "proto3";message Test {oneof message {MessageA a = 1;MessageB b = 2;}
}message MessageA {string content = 1;
}
message MessageB {int32 content = 1;
}

  编译生成oneof_test_pb2.py。现在需要将一个Test消息的a字段设置为MessageA,另一个Test消息的b字段设置为MessageB,代码(set_onof_field)如下:

# -*- coding: utf-8 -*-
from oneof_test_pb2 import Test, MessageA, MessageBmessage_a = MessageA()
message_a.content = "Hello from MessagesA!"message_b = MessageB()
message_b.content = 1# 设置test.a为MessageA
test1 = Test()
test1.a.CopyFrom(message_a)
# 设置test.a为MessageB
test2 = Test()
test2.b.CopyFrom(message_b)print(test1)
print(test2)

输出结果如下:

a {content: "Hello from MessagesA!"
}b {content: 1
}

总结

  本文演示的例子都已放至Github,访问地址为:https://github.com/percent4/protobuf_learning 。

  本文到此结束,感谢大家的阅读~

参考网址

  1. python基础–protobuf的使用(一): https://blog.csdn.net/u013210620/article/details/81317731
  2. Github/protobuf: https://github.com/protocolbuffers/protobuf
  3. gRPC快速入门(一)——Protobuf简介: https://blog.51cto.com/9291927/2331980?source=drh
  4. Protobuf3语法详解: https://blog.csdn.net/qq_36373500/article/details/86551886
  5. protobuf文本格式解析地图: http://www.voidcn.com/article/p-yrhvscxz-bwp.html
  6. Question: How to set oneof fields in python?: https://github.com/protocolbuffers/protobuf/issues/5012

Protobuf学习入门(一)相关推荐

  1. 【AI参赛经验】深度学习入门指南:从零开始TinyMind汉字书法识别——by:Link

    各位人工智能爱好者,大家好! 由TinyMind发起的#第一届汉字书法识别挑战赛#正在火热进行中,比赛才开始3周,已有数只黑马冲进榜单.目前TOP54全部为90分以上!可谓竞争激烈,高手如林.不是比赛 ...

  2. 深度学习入门,一文讲解神经网络的构成、训练和算法

    小白深度学习入门系列 神经网络的构成.训练和算法 什么是神经网络 人工神经网络(Artificial Neural Network,ANN),简称神经网络(Neural Network,NN),是一种 ...

  3. PyTorch深度学习入门与实战(案例视频精讲)

    作者:孙玉林,余本国 著 出版社:中国水利水电出版社 品牌:智博尚书 出版时间:2020-07-01 PyTorch深度学习入门与实战(案例视频精讲)

  4. PyTorch深度学习入门

    作者:曾芃壹 出版社:人民邮电出版社 品牌:iTuring 出版时间:2019-09-01 PyTorch深度学习入门

  5. 深度学习入门 基于Python的理论与实现

    作者:斋藤康毅 出版社:人民邮电出版社 品牌:iTuring 出版时间:2018-07-01 深度学习入门 基于Python的理论与实现

  6. 干货|《深度学习入门之Pytorch》资料下载

    深度学习如今已经成为了科技领域中炙手可热的技术,而很多机器学习框架也成为了研究者和业界开发者的新宠,从早期的学术框架Caffe.Theano到如今的Pytorch.TensorFlow,但是当时间线来 ...

  7. 福利丨一门面向所有人的人工智能公开课:MIT 6.S191,深度学习入门

    对初学者来说,有没有易于上手,使用流行神经网络框架进行教学的深度学习课程?近日,麻省理工学院(MIT)正式开源了在线介绍性课程「MIT 6.S191:深度学习入门」.该课程包括一系列有关神经网络及其在 ...

  8. 深度学习入门指北——从硬件到软件

    作者:隔壁王大喵 近日,Rachel Thomas在fast.ai上发布了一篇博文<What you need to do deep learning>,他希望通过这篇文章回答一些深度学习 ...

  9. LeCun亲授的深度学习入门课:从飞行器的发明到卷积神经网络

    Root 编译整理 量子位 出品 | 公众号 QbitAI 深度学习和人脑有什么关系?计算机是如何识别各种物体的?我们怎样构建人工大脑? 这是深度学习入门者绕不过的几个问题.很幸运,这里有位大牛很乐意 ...

最新文章

  1. LeetCode 905 Sort Array By Parity--Java stream,Python lambda表达式一行 解法
  2. 三、HDFS中的Java和Python API接口连接
  3. 『Numpy』内存分析_高级切片和内存数据解析
  4. TensorFlow学习笔记(十九) 基本算术运算和Reduction归约计算
  5. zw版【转发·台湾nvp系列Delphi例程】HALCON DispCross
  6. JavaFX 一 出生新手村(阅读小规则)
  7. BAT大神推荐:看懂英文文档,每天只需要10分钟做这件事……
  8. vue项目使用sass-loader
  9. 干货分享 | 史上最全Oracle体系结构整理
  10. 【案例实战】餐饮企业分店财务数据分析系统解决方案:业务需求
  11. 图像处理领域的国际会议及期刊
  12. xPath(他山之石)
  13. dialog问题记录
  14. masql redis
  15. segment fault 至core dump的原因
  16. Json对象和Json字符串的区别
  17. delphi android 打印机,delphi中如何检测打印机状态?(在线等) ( 积分: 100 )
  18. python迭代器与生成器答案,彻底搞懂python 迭代器和生成器
  19. 第一个安卓应用小程序--浅浅仿照微信发现界面
  20. 在线工作计划安排日历表工具

热门文章

  1. ewt分解模式matlab算法如何实现,EWT 经验小波分解 在 的基础上提出的一个拓展,具有自适应性,效果有提升 matlab 276万源代码下载- www.pudn.com...
  2. 围棋的基本手法及术语
  3. MySQL期中考试上机试题
  4. Android NDK开发之C语言基础及指针①
  5. 互联网“铁王座”争夺史
  6. 最新iscsi服务器,适用于Windows的一款先进,强大,多功能的iscsi 服务器软件。
  7. MatLab计算图像圆度
  8. Mac OS删除文件夹和文件的命令
  9. linux-mips启动分析,linux mips启动分析 - MIPS技术及应用社区
  10. 架构师面试,问到你怀疑人生!