在Python中使用protocol buffers参考指南

2023-12-22 17:32

本文主要是介绍在Python中使用protocol buffers参考指南,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Protocol Buffer Basics: Python

本教程提供了一个Python程序员使用protocol buffers的基本的入门教程。通过创建一个简单的示例应用程序,它向您展示了如何

*在一个.proto文件中定义Message的格式。

*使用protocol buffer compiler。

*使用Python protocol buffer API去读写Message

这不是一个在Python中使用protocol buffers的一个全面的指南。如果想了解更详细的参考信息,请阅读 Protocol Buffer Language GuidePython API Reference,Python Generated Code GuideEncoding Reference。

Why Use Protocol Buffers?

我们将使用的示例是一个非常简单的“地址簿”应用程序,可以 从一个文件中读写人们的联系方式。地址簿中的每个人都有一个名字,一个ID、一个电子邮件地址,和联系电话号码。

你怎样用这样方式序列化和检索结构数据?这里有一些办法可以解决这个问题:

*使用Python处理。这是默认的方法,因为这种方法是直接用到语言,但它不利于模式演变,还有,它不利于你共享数据给c++或Java写的应用。

*你可以发明一种特别的方式将数据项编码为一个字符串,如将4个int编码为“12:3:23:67”。这是一个简单的和灵活的方法,尽管它一次性需要编写编码和解析的代码,并为解析加上一个小小的运行成本。这方法最适合为非常简单的数据编码

*用XML序列化数据。这种方法非常有吸引力,因为XML具有易读性,还有了许多库,用来支持各种语言。这是一个好选择,如果你想和其它应用/工程共享数据。但是,XML也是出了名的耗空间,还有,编码/解码会令应用程序性能产生巨大的损失。加上,操纵一个XML DOM树通常会比操纵类中的字段复杂。

Protocol buffers会灵活、高效、自动化解答来准确地解决这个问题。有了protocol buffers,你就可以编写一个.proto文件用来描述你想存储的数据结构。因此,protocol buffer编译器会创建一个类,实现自动编码和解析protocol buffer数据,通过一个高效的二进制格式。这个生成的类提供了getter和setter的字段组成一条protocol buffer,而且把读出和写入的细节当成protocol buffer的一个单元。更重要的,protocol buffer支持在日后里扩展格式这种想法,这样,代码仍然可以读取用旧的格式编码的数据。

Where to Find the Example Code

在源代码目录中,文件夹“examples”下包含所有的例程。 Download it here.

Defining Your Protocol Format

为了创建你的“地址簿”应用,你会用到一个.proto文件。这是一个很简单的.proto文件定义:你可以为你想序列化的数据结构添加一条 Message ,然后在Message中为每个字段 指定一个名称和一个类型。以下是你想为你的Message定义的.proto文件,addressbook.proto。

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package tutorial;  
  2.   
  3. message Person {  
  4.   required string name = 1;  
  5.   required int32 id = 2;  
  6.   optional string email = 3;  
  7.   
  8.   enum PhoneType {  
  9.     MOBILE = 0;  
  10.     HOME = 1;  
  11.     WORK = 2;  
  12.   }  
  13.   
  14.   message PhoneNumber {  
  15.     required string number = 1;  
  16.     optional PhoneType type = 2 [default = HOME];  
  17.   }  
  18.   
  19.   repeated PhoneNumber phone = 4;  
  20. }  
  21.   
  22. message AddressBook {  
  23.   repeated Person person = 1;  
  24. }  

如你所见,在语法上很像C++和Java。那就让我们看看文件中的每个部分和看看它们究竟是干什么的。

这个.proto文件开头是包的声明,为了帮助防止在不同的工程中命名冲突。在Python中,包通常由目录结构决定的,所以这个由你的.proto文件定义的包,在你生成你代码中是没有效果的。但是,你应该坚持声明这条语句,为了在protocol Buffers的命名空间中防止名子的冲突,就像其它非Python的语言那样。

然后,就是你定义的Message。一个Message是一个包含一组类型字段的集合。有许多简单的标准的数据类型可以用在类型字段中,包括bool,int32,float,double和string。你也可以使用更加多的结构来定义你的Message,例如用其它Message类型当作类型字段-在上面的例子PersonMessage中就包了PhoneNumberMessage,还有AddressBookMessage包含PersonMessage。你也可以定义Message嵌入其它的Message——就如你所见到的那样,PhoneNumber类型就是在Person类型中定义的。你也可以定义一个枚举类型,如果你想你其中一个字段有一个预设的类型列表——在这里,你可以将你的电话号码列举为MOBILE,HOME或者WORK。

那个“=1”,“=2”标记每个元素的识别,作为二进制编码中字段的唯一的标签。标签要求数字1-15比更高的数字少一个字节编码,所以,作为最优化的方案,你可以决定对常用的和要重复使用的元素使用这些标签,把16或最高的数字留给不常用和可选择的元素。每个重复的字段里的元素要求重新编码它的标签号码,所以重复的字段特别适合使用这种优化。

每个字段一定要被以下的修饰语修饰:

*required:一定要提供一个值给这个字段,否则这条Message会被认为“没有初始化”。序列化一列没有初始化的Message会出现异常。 解析一条没有初始化的Message会失败。除此而外,这个required字段的行为更类似于一个optional字段。

*optional:这个字段可以设置也可以不设置 。如果一个可选字段没有设置值,会用缺省的值。简单来说,你可以指定自己的默认值,就像我们在例子中对phone number类型所做的。另外,系统会缺省这样做:0给整数类型,空串给字符串类型,false给布尔类型。对于嵌入的Message缺省的值通常会是“默认实例”或“原型”,对那些没有设置字段的Message。调用存取器获得一个可选的(或要求)字段的值,那些通常什么明确给出值的字段总是返回该字段的默认值。

*repeated:这个字段会重复几次一些号码(包括0)。重复的值给按顺序保存在protocol buffer中。重复的字段会被认为是动态的数组。

Required Is Forever 你应该非常小心地把字段标记为required。如果在某一时刻你希望停止写或发送一个必填字段,那就把不确定的字段更改为一个可选的字段——老的阅读器会认为没有这个字段Message是不完整的,而且可能会无意中拒绝或删除它们。你应该考虑为你的buffer编写特定于应用程序的自定义验证例程。一些来自Google有些结论:使用required弊大于利;他们更愿意只用optional和repeated。但是,这一观点并不普遍。

你会找到编写.proto文件的指南——包括所有可能的类型字段——在Protocol Buffer Language Guide.不要去找类似于类继承的设备,虽然——protocol buffers不这样做。

Compiling Your Protocol Buffers

现在你有了自己的.proto文件,下一件你需要去做的事就是生成你需要读写AddressBook(还带有Person和PhoneNumber) Message的类。为了完成这件工作,你需要运行protocol buffer 编译器protoc去编译你的.proto文件:

1.如果你没有安装编译器,download the package,按照在README的说明去做

2.现在运行编译器,指定源目录(你的应用程序源码目录——如果你不提供这个目录,默认就是当前目录),目标目录(你的应用程序编译后生成的代码的目录;通常用$SRC_DIR),还有你.proto文件的目录路径。在这种情况下,你可以

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto  

因为你想生成Python的类,所以你要用--python_out选项——也有类似的选项支持其它语言。

这样addressbook_pb2.py就会生成在你指定的目标目录中。

The Protocol Buffer API

不你你生成的Java或C++的protocol buffer代码,Python protocol buffer编译器不会直接生成你可以数据访问的代码。反而(就你看见的那样,如果你看了addressbook_pd2.py)它会为你的 Message,枚举,字段生成指定的描述符,还有一些难以理解的空类,其中一段 Message类型:

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class Person(message.Message):  
  2.   __metaclass__ = reflection.GeneratedProtocolMessageType  
  3.   
  4.   class PhoneNumber(message.Message):  
  5.     __metaclass__ = reflection.GeneratedProtocolMessageType  
  6.     DESCRIPTOR = _PERSON_PHONENUMBER  
  7.   DESCRIPTOR = _PERSON  
  8.   
  9. class AddressBook(message.Message):  
  10.   __metaclass__ = reflection.GeneratedProtocolMessageType  
  11.   DESCRIPTOR = _ADDRESSBOOK  

每个类中都有一些重要的语句: __metaclass__ = reflection.GeneratedProtocolMessageType .尽管Python中metaclasses是如何工作的详细信息超出了本教程的范围,你可以把它们看作是创建类的模板。在加载时,GeneratedProtocolMessageType  metaclass 会用指定的描述符创建所有你需要用到的Message类型的Python方法和添加和这些方法相关的类。然后你就可以在你的代码中使用这些类。

这一切的最终效果是,你可以使用Person类就像你定义的Message的基类,将它当作常规的字段。例如,你可以这样写:

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. import addressbook_pb2  
  2. person = addressbook_pb2.Person()  
  3. person.id = 1234  
  4. person.name = "John Doe"  
  5. person.email = "jdoe@example.com"  
  6. phone = person.phone.add()  
  7. phone.number = "555-4321"  
  8. phone.type = addressbook_pb2.Person.HOME  

注意,这些赋值不仅仅是一个在通用的Python对象中添加任意的新字段。如果你尝试 赋值一些值到.proto文件没有定义的字段变量中,会发生AttributeError 异常。如果你赋给一个字段错误的类型的值,会发生TypeError 异常。当然,同时,在看一个字段的值前,这个字段就已经设置为返回默认值。

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. person.no_such_field = 1  # raises AttributeError  
  2. person.id = "1234"        # raises TypeError  

如果想知道更多关于protocol 编译器为特定定义字段生成的成员,请看 Python generated code reference .

Enums

Enums是由metaclass扩展成一组具有符号常量的整数值。那么,举个例子,不变量 addressbook_pb2.Person.WORK 有一个值为2.

Standard Message Methods

每个 Message 类都包含一些其它方法让你检查或操作 entire message,包括:

  • IsInitialized(): 检查required字段是否都设置了值。
  • __str__(): 返回一个可读的message,对于调试尤其有用。(通常调用str(message)或打印message。)
  • CopyFrom(other_msg): 用给出的message的值覆盖这个message
  • Clear(): 清除所有元素,回到空状态。
    这些方法是操作Message的接口,想了解更多,可以看complete API documentation for Message.

    Parsing and Serialization

    最后,每个protocol buffer类都有读message或写message的方法给你选择通过用protocol buffer binary format。包括:
    • SerializeToString(): 序列化这个message和以字符串的方式返回。 注意,这是二进行字节,不是一个文本; 我们只使用str类型作为一个方便的容器。
    • ParseFromString(data): 从给出的字符串中解析一条message。
    这里有只是一些使用解析和序列化的选项。此外,也会以看 Message API reference,查看完整的列表。

    Protocol Buffers和O-O Design Protocol buffer 类基本上是dumb data holders(类似于C++的structs);它们在对象模型虽不做好first class citizens。如果你想为已生成的类添加丰富的行为,更好的方法是把已生成的protocol buffer类封装在一个特定于应用程序的类。封装protocol buffers是一个好想法,如果你不会控制.proto file的设计(如果说,你征用其它工程的代码)。在这种情况下,您可以使用封装类去制作接口会更适合您的应用程序,在独特环境中:隐藏一些数据和方法,暴露便利的函数,等等。你绝不应该添加行为通过继承已生成的类。这将打破内部机制和没有良好的面向对象的体验。

    Writing A Message

    现在可以尝试用你的protocol buffer 类了。首先你第一件事你想让你的地址簿应用程序能够做的事情就是把个人信息写到你的地址簿文件里。为此,你创建和填写你的protocol buffer类的实例,然后把它们写到输出流。
    以下是从AddressBook文件中读一个程序,按用户的输入添加一个新Person,和写一个新AddressBook再一次返回这个文件。部分直接调用或引用的从protocol compiler生成的代码给出了高亮显示。
    #! /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.phone.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.person.add())# Write the new address book back to disk.
    f = open(sys.argv[1], "wb")
    f.write(address_book.SerializeToString())
    f.close()

    Reading A Message

    当然,一个地址簿,如果你不能从中得到任何信息,那也不会有多大用处!这个例子为从上面的示例读取文件并打印其所有信息。
    #! /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.person:print "Person ID:", person.idprint "  Name:", person.nameif person.HasField('email'):print "  E-mail address:", person.emailfor phone_number in person.phone: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)

    Extending a Protocol Buffer

    迟早你会发布使用protocol buffer的代码,毫无疑问你会想“改善” protocol buffer的定义。如果你想让你的新buffers 是向后兼容的,又或者你的旧buffers是向前兼容的-那你就肯定想这样做-那这里有些规则你需要遵守了。在新版本的protocol buffer中:
    • 你不能改变任何现有的标签数据字段。
    • 你不能添加或删除任何repeated的字段。
    • 你可以删除optional 或repeated 字段。
    • 你可以添加新的optional 或repeated 字段但你必须使用新的标签号码。(即在protocol buffer从来没有使用过的标记数字,甚至通过删除字段)
    这些规则有一些例外,但他们很少使用
    如果你遵守这些规则,旧的代码会很高兴地读新的messages并简单地忽略所有新字段。对于旧代码来说,被删除的可选optional 字段,简单地拥有它们的默认值,被删除的repeated字段会变为空。新代码会明显地读到旧代码。但是,记住,新的optional字段不会显示旧的message,所以你需要明确地检查它们是否有has_设置,又或者提供一个合理的缺省的值在[default = value]的标签数字后,在你的.proto文件上。如果没有为一个optional无线设置缺省的值,一个字符串的值会替代缺省的值,这个缺省的值则为一个空串。对于布尔类型来说,缺省为false。对于整型类型来说,缺省为0。还有记住,如果你添加一个新repeated字段,你的新代码将无法判断它是空的(通过新代码)或从未设置(旧代码)由于没有设置has_ flag给它。

    本文翻译自:https://developers.google.com/protocol-buffers/docs/pythontutorial
  • 本文转载自:点击打开链接

这篇关于在Python中使用protocol buffers参考指南的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/524839

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了