Protocol Buffer 基础(Python 版)

翻译自:https://developers.google.com/protocol-buffers/docs/pythontutorial

需要使用 protocol buffer 主要分为以下三步:

  • 通过 message 格式定义 .proto 文件
  • 使用 protocol buffer 编译器生成 .py 文件(其他语言类似)
  • 使用 Python protocol buffer API 读写 messages

本文只是基础介绍详细信息可参考:

  • 详细文档:https://developers.google.com/protocol-buffers/docs/overview
  • 版本下载:https://developers.google.com/protocol-buffers/docs/downloads

protocol buffer 的优势

  • Python 内置 pickling:不能很好的应对 schema 的演进,而且不能很好的与 C++ 或者 Java 程序进行数据共享
  • 可以自定义一种转换为字符串的方式。虽然其需要编写编码和解析的代码,并且解析需要一定的时间成本。但因为其简单灵活特别适合处理非常简单的数据。
  • XML:XML 由于易读性并且兼容多种语言而被使用。不过其需要占用相当大的存储空间,同时编解码也需要付出大量的时间成本,最后遍历 XML DOM 树相比于遍历某个类也要复杂的多。

定义 Protocol 格式

这里以一个地址簿的例子做说明。其中地址簿中包含多个联系人,每个联系人将包含一个姓名,ID,电子邮箱和联系电话。这样一个地址簿的 .proto 文件定义如下。这里的例子是以 proto2 为基础,最新的版本为 proto3。

// addressbook.proto
syntax = "proto2";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 phones = 4;
}message AddressBook {repeated Person people = 1;
}

.proto 文件首先指定包名,其帮组解决不同项目中可能存在的命名冲突问题。不过由于 Python 通过文件路径来管理包名。因此这部分内容只在非 Python 语言中起作用。

接下来就是 message 的定义。message 提供了很多标准的简单数据类型,比如 bool, int32, float, double, string。当然也可以定义一下复杂的数据结构,比如上述程序中,Person 中就包括了 PhoneNumber;同时 AddressBook 包含了 Person。也可以通过枚举预定义变量的取值。

而变量之后的 ‘=1’, '=2' 给出了每个变量唯一的标示。同时,由于 1 - 15 使用的编码大小比 16 以上的少一个字节,因此通常将 1 - 15 留给 required 和 repeated 域。同时,由于 repeated 域中的每一个元素都需要进行标示的重编码,因此这一优化十分适合 repeated 域。

同时,上面的例子中可以发现每一个域都需要一个如下的修饰符:

  • required:这个域必须被赋值,如果未被赋值,那么如果序列化一个这样的 message 将返回一个异常,而如果解析一个未初始化的 message 将会失败。除此之外, required 与 optional 并没有实质的差别。
  • optional:这个域的赋值可有可无。如果没有被赋值,那么默认赋值将被使用。对于简单类,我们可以自己指定,比如上例中的 type。同时每个简单类都提供了一个系统默认的初始值,比如数值类型(0),字符串(空字符串),逻辑变量(false)。对于嵌入式 message,默认值始终是 message 的默认实例或者原型,其中并没有设置其域。如果访问没有显式赋值的域,将总是得到其默认值。
  • repeated:这个可以重复任意次(包括 0)。重复值的顺序将被存储在 protocol buffer 中。repeated 可以被理解为动态数组。

这里必须提醒一点,required 是永久的:因此,在定义域为 required 的时候必须十分谨慎,因为之后如果需要修改,那么使用旧程序的使用者将认为 message 没有正确赋值而报错。同时 Google 内部也就是否需要保留 required 而进行讨论。好像 protocol buffer 3 已经不再显式指明这些修饰符了。

编译 Protocol Buffers

你可以从以下网站  https://developers.google.com/protocol-buffers/docs/downloads 并遵循说明安装编译器。

protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto

参数说明:

  • $SRC_DIR:应用所代码,如果不提供将使用当前路径
  • $DST_DIR:生成的代码希望放到哪。由于本例基于 Python,因此使用 --python_out,其他语言类似。
  • 最后是指向 .proto 文件的路径

这行命令将生成一个 addressbook_pb2.py 文件。

Protocol Buffer API

对于 Java 和 C++,当生成 protocol buffer 代码时,将直接给出访问数据的代码,但对于 Python 并不会给出。addressbook_pb2.py 包含以下内容:

class Person(message.Message):__metaclass__ = reflection.GeneratedProtocolMessageTypeclass PhoneNumber(message.Message):__metaclass__ = reflection.GeneratedProtocolMessageTypeDESCRIPTOR = _PERSON_PHONENUMBERDESCRIPTOR = _PERSONclass AddressBook(message.Message):__metaclass__ = reflection.GeneratedProtocolMessageTypeDESCRIPTOR = _ADDRESSBOOK

每一个类中重要的信息是 __metaclass__ = reflection.GeneratedProtocolMessageType。具体 Python metaclasses 如何发挥功效可能超过了本文的范围,但你可以把它理解为创建类时需要使用的模板。在加载时,GeneratedProtocolMessageType metaclasses 使用特定的描述符产生所有你需要的 Python 方法并将它们添加到对应的类中。之后就可以在你的代码中使用完全填充好的类了。

比如本文之前使用过的例子,我们就可以通过如下方式使用 message 中定义的 Person 类。

import addressbook_pb2
person = addressbook_pb2.Person()
person.id = 1234
person.name = "John Doe"
person.email = "jdoe@example.com"
phone = person.phones.add()
phone.number = "555-4321"
phone.type = addressbook_pb2.Person.HOME

上述赋值过程并不是简单的增加类中的成员,如果你视图赋值一个没有的变量将返回 AttributeError,而如果你分配了一个错误的类型,那么将返回 TypeError。如果在赋值之前访问一个变量,将返回其默认值。

person.no_such_field = 1  # raises AttributeError
person.id = "1234"        # raises TypeError

枚举类

枚举类由元类扩展为一组具有整数值的符号常量。比如上述例子中 addressbook_pb2.Person.WORK 有一个值 2.

标准 message 方法

每一个 message 还包含某些函数让你可以检查或者操作整个 message。

  • IsInitialized():检测所有的 required 是否被赋值
  • __str__():返回一个可读的 message 表示,通常用于调试中 str(message)和print(message)

解析与序列化

  • SerializeToString():序列化一个 message 并返回字符串。返回的字节是二值的并不是文本,只能使用 str 类型进行存储
  • ParseFromString(data):从给定的字符串中解析 message

其他的解析和序列化函数可以参考:https://developers.google.com/protocol-buffers/docs/reference/python/google.protobuf.message.Message-class

需要注意的是基于 O-O 设计的:Protocol Buffer 本质上是 dumb 的数据存储器,这点有点类似于 C 语言的结构体,这表明其并不能很好的兼容对象模型。如果你想要增加更丰富的行为到一个扩展类中,最好的方式是在应用类中封装扩展的 protocol buffer 类。如果你是复用另一个项目中的 protocol buffer 而无法控制 .proto 文件的设计,那么封装也是一个很好的方式。通过封装,可以更好地适应具体应用的特定环境,比如隐藏某些数据和方法,开放某些方便的函数。但你绝对不要通过继承的方式来扩展类。这将破坏内部的结构并不是一个好的面向对象的方式。

message 读写

#! /usr/bin/pythonimport addressbook_pb2
import sys# This function fills in a Person message based on user input.
def PromptForAddress(person):person.id = int(raw_input("Enter person ID number: "))person.name = raw_input("Enter name: ")email = raw_input("Enter email address (blank for none): ")if email != "":person.email = emailwhile True:number = raw_input("Enter a phone number (or leave blank to finish): ")if number == "":breakphone_number = person.phones.add()phone_number.number = numbertype = raw_input("Is this a mobile, home, or work phone? ")if type == "mobile":phone_number.type = addressbook_pb2.Person.MOBILEelif type == "home":phone_number.type = addressbook_pb2.Person.HOMEelif type == "work":phone_number.type = addressbook_pb2.Person.WORKelse:print "Unknown phone type; leaving as default value."# Main procedure:  Reads the entire address book from a file,
#   adds one person based on user input, then writes it back out to the same
#   file.
if len(sys.argv) != 2:print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"sys.exit(-1)address_book = addressbook_pb2.AddressBook()# Read the existing address book.
try:f = open(sys.argv[1], "rb")address_book.ParseFromString(f.read())f.close()
except IOError:print sys.argv[1] + ": Could not open file.  Creating a new one."# Add an address.
PromptForAddress(address_book.people.add())# Write the new address book back to disk.
f = open(sys.argv[1], "wb")
f.write(address_book.SerializeToString())
f.close()
#! /usr/bin/pythonimport addressbook_pb2
import sys# Iterates though all people in the AddressBook and prints info about them.
def ListPeople(address_book):for person in address_book.people:print "Person ID:", person.idprint "  Name:", person.nameif person.HasField('email'):print "  E-mail address:", person.emailfor phone_number in person.phones:if phone_number.type == addressbook_pb2.Person.MOBILE:print "  Mobile phone #: ",elif phone_number.type == addressbook_pb2.Person.HOME:print "  Home phone #: ",elif phone_number.type == addressbook_pb2.Person.WORK:print "  Work phone #: ",print phone_number.number# Main procedure:  Reads the entire address book from a file and prints all
#   the information inside.
if len(sys.argv) != 2:print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"sys.exit(-1)address_book = addressbook_pb2.AddressBook()# Read the existing address book.
f = open(sys.argv[1], "rb")
address_book.ParseFromString(f.read())
f.close()ListPeople(address_book)

扩展现有的 protocol buffer

在你发布你的 protocol buffer 代码之后,不可避免的将会出现需要升级代码的情况,此时如果你需要升级后的代码在之前的代码中仍然可以正常运行(向后兼容),那么升级代码必须满足以下要求:

  • 一定不能改变任何现有域的标示数字(1,2,3,...)
  • 一定不能增加或删除任何的 required 域
  • 可以删除 optional 或者 repeated 域
  • 可以增加新的 optional 或者 repeated 域,但必须保证使用新的标示数字。新的标示数字必须从未被使用,即使被删除的域使用过的标示数字也不行
  • 还有一个些其他的规则,但都不怎么会遇到,如果需要可以参考 https://developers.google.com/protocol-buffers/docs/proto#updating

如此旧代码就可以正常的读取新 meassge 并简单忽略任何新的域。同时就代码对于已经删除的 optional 域将直接使用默认值,而 repeated 域将被置为空。而新代码也将直接读取旧 message。然而必须注意新的 optional 域在旧代码中并不会被提供,因此你必须显式地访问标示位 has_ 来判断其是否被赋值或者通过在标示数字之后使用 [default = value] 来指定默认值。如果默认值没有被指定,那么系统默认值将被使用。同时,对于 repeated 域,由于其没有 has_ 标示位,因此无法获知其是因为新代码没有赋值还是因为旧代码根本就没有设置造成为空。

高阶使用

可以通过如下网站 https://developers.google.com/protocol-buffers/docs/reference/python/ 获取更丰富的使用说明。

其中一个重要的特性就是反射(reflection)。你可以迭代 message 中的域并修改他们的值而不必针对任何特定的 message 类型而修改你的代码。一个很有用的功能是使用反射来实现 protocol buffer 和其他编码格式,比如 XML 或者 JSON 之间的相互转换。而一个高阶的功能是反射可用来找出相同 message 类型中的差异,或者开发出一系列 protocol message 的常规表示,在其中你可以编写匹配特性 message 内容的表达式。同时,你可以发挥自己的想象力从而使用 protocol buffer 来解决更多你可能遇到的问题。

关于反射的详细介绍可以参考:https://developers.google.com/protocol-buffers/docs/reference/python/google.protobuf.message.Message-class

Protocol Buffer 基础(Python 版)相关推荐

  1. spark编程基础python版 pdf_Spark编程基础Python版-第5章-Spark-SQL.pdf

    <Spark编程基础(Python版)> 教材官网:/post/spark-python/ 温馨提示:编辑幻灯片母版,可以修改每页PPT的厦大校徽和底部文字 第5章Spark SQL (P ...

  2. [翻译]Protocol Buffer 基础: C++

    目录 Protocol Buffer Basics: C++ 为什么使用 Protocol Buffers 在哪可以找到示例代码 定义你的协议格式 编译你的 Protocol Buffers Prot ...

  3. Spark编程基础(Python版)

    一.掌握spark的安装与环境配置 二.掌握spark的安装与环境配置 三.掌握Ubuntu下的Python的版本管理与第三方的安装 四.掌握windows下Pycharm与Ubuntu的同步连接 五 ...

  4. spark编程基础python版实验报告_Spark编程基础(Python版)

    章 大数据技术概述 1.1 大数据概念与关键技术 1.1.1 大数据的概念 1.1.2 大数据关键技术 1.2 代表性大数据技术 1.2.1 Hadoop 1.2.2 Spark 1.2.3 Flin ...

  5. Protocol Buffer Basics: C#

    Protocol Buffer 基础知识:c#    原文地址:https://developers.google.com/protocol-buffers/docs/csharptutorial 这 ...

  6. 只用2000行代码实现google protocol buffer c++版的功能

    2019独角兽企业重金招聘Python工程师标准>>> google protocol buffer (下面简称gpb)功能强大,应用广泛,但在实际应用中,gpb需要写.proto脚 ...

  7. python中使用 protocol buffer(Protobuf)

    项目中引入proto的依赖 [两种方法]: 方法1. 官网下载对应的语言包,https://github.com/protocolbuffers/protobuf/releases 这里选择pytho ...

  8. 爆肝5万字❤️Open3D 点云数据处理基础(Python版)

    Open3D 点云数据处理基础(Python版) 文章目录 1 概述 2 安装 2.1 PyCharm 与 Python 安装 2.3 Anaconda 安装 2.4 Open3D 0.13.0 安装 ...

  9. python自动化办公入门书籍-视频教程-零基础Python自动化办公(漫画版)-Python

    零基础Python自动化办公(漫画版) 现任某大型游戏公司后端工程师,阿里云大学云学院导师,中国人工智能协会高级会员,HackPython工作室负责人,曾出版书籍<深入浅出生成对抗网络:原理剖析 ...

  10. 没有统计学基础可以学python-没错!经典教材《统计学习导论》现在有了 Python版!...

    点击关注"Python学习与数据挖掘" 更多超级干货第一时间推送给你哦!!! <统计学习导论>很经典,但用的是 R 语言,没关系,这里有份 Python 版习题实现. ...

最新文章

  1. Go语言的错误异常处理机制及其应用
  2. Python 进度条 tqdm
  3. paper structure for innovation management
  4. iOS-NSData与NSDictionary的互相转换
  5. bin文件转换成html,怎么样把BIN文件转换成ISO文件
  6. 半价秒杀,最后一天!戴尔i7高配电脑低至2750元!
  7. 受 SQLite 多年青睐,C 语言到底好在哪儿?
  8. knn算法python代码_K-最近邻分类算法(KNN)及python实现
  9. React之回调ref中回调执行次数的问题
  10. [转载]Windows 2012 R2安装SharePoint 2013 手动安装工具软件
  11. Exchange2010配置实验(二)满足先决条件安装Exchange2010
  12. 一个删除文件的批处理
  13. Repast-边界控制
  14. 人大金仓数据库使用uuid
  15. NLP思维一书读书笔记
  16. 牛客网 - [牛客假日团队赛6]对牛排序
  17. linux开发板通过网线连接电脑(win10)连接网络问题
  18. C语言 平面向量加法
  19. 斗地主的Java实现
  20. 神经网络入门经典书籍,神经网络基础书籍

热门文章

  1. 腾讯企业邮箱发送邮件php,Laravel5.* 使用Smtp发送邮件以及常见报错解决(腾讯企业邮箱、163、QQ、Gmail 等) - Laravel学习网...
  2. 怎么管理好精力,让自己每天精力充沛
  3. Character controller
  4. 经典的机器人入门资料
  5. 【数论】【不定方程】n元一次不定方程、佩尔方程、毕达哥拉斯定理、费马大定理
  6. BLUES吉他学习笔记001 bluesrv[1-5]
  7. Mstar的Monitor方案笔记(七)——EDID基本数据结构
  8. WEB页面打印--打印指定区域,页面预览,页面设置
  9. Fiddler手机APP抓包及无法连接网络问题处理
  10. php 豆瓣抓取,PHP抓取豆瓣读书爬虫代码