发布时间:2019-07-29 10:28:41编辑:auto阅读(1378)
本教程提供了一个Python程序员使用protocol buffers的基本的入门教程。通过创建一个简单的示例应用程序,它向您展示了如何
*在一个.proto文件中定义Message的格式。
*使用protocol buffer compiler。
*使用Python protocol buffer API去读写Message。
这不是一个在Python中使用protocol buffers的一个全面的指南。如果想了解更详细的参考信息,请阅读 Protocol Buffer Language Guide,Python API Reference,Python Generated Code Guide和Encoding Reference。
你怎样用这样方式序列化和检索结构数据?这里有一些办法可以解决这个问题:
*使用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支持在日后里扩展格式这种想法,这样,代码仍然可以读取用旧的格式编码的数据。
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 phone = 4;
}
message AddressBook {
repeated Person person = 1;
}
这个.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不这样做。
1.如果你没有安装编译器,download the package,按照在README的说明去做。
2.现在运行编译器,指定源目录(你的应用程序源码目录——如果你不提供这个目录,默认就是当前目录),目标目录(你的应用程序编译后生成的代码的目录;通常用$SRC_DIR),还有你.proto文件的目录路径。在这种情况下,你可以
protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto
这样addressbook_pb2.py就会生成在你指定的目标目录中。
class Person(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
class PhoneNumber(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
DESCRIPTOR = _PERSON_PHONENUMBER
DESCRIPTOR = _PERSON
class AddressBook(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
DESCRIPTOR = _ADDRESSBOOK
__metaclass__ = reflection.GeneratedProtocolMessageType
.尽管Python中metaclasses是如何工作的详细信息超出了本教程的范围,你可以把它们看作是创建类的模板。在加载时,GeneratedProtocolMessageType metaclass
会用指定的描述符创建所有你需要用到的Message类型的Python方法和添加和这些方法相关的类。然后你就可以在你的代码中使用这些类。
这一切的最终效果是,你可以使用Person类就像你定义的Message的基类,将它当作常规的字段。例如,你可以这样写:
import addressbook_pb2
person = addressbook_pb2.Person()
person.id = 1234
person.name = "John Doe"
person.email = "jdoe@example.com"
phone = person.phone.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
IsInitialized()
: 检查required字段是否都设置了值。
__str__()
: 返回一个可读的message,对于调试尤其有用。(通常调用str(message)或打印message。)
CopyFrom(other_msg)
: 用给出的message的值覆盖这个message
Clear()
: 清除所有元素,回到空状态。Message
.
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的设计(如果说,你征用其它工程的代码)。在这种情况下,您可以使用封装类去制作接口会更适合您的应用程序,在独特环境中:隐藏一些数据和方法,暴露便利的函数,等等。你绝不应该添加行为通过继承已生成的类。这将打破内部机制和没有良好的面向对象的体验。
#! /usr/bin/python import 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 = email while True: number = raw_input("Enter a phone number (or leave blank to finish): ") if number == "": break phone_number = person.phone.add() phone_number.number = number type = raw_input("Is this a mobile, home, or work phone? ") if type == "mobile": phone_number.type = addressbook_pb2.Person.MOBILE elif type == "home": phone_number.type = addressbook_pb2.Person.HOME elif type == "work": phone_number.type = addressbook_pb2.Person.WORK else: 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()
#! /usr/bin/python import 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.id print " Name:", person.name if person.HasField('email'): print " E-mail address:", person.email for 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)
上一篇: Python 3.7之使用web api
下一篇: Python 3.7:数据类的介绍
47490
45792
36789
34321
28958
25594
24441
19608
19109
17630
5463°
6046°
5568°
5636°
6571°
5374°
5375°
5882°
5853°
7167°