官方:https://developers.google.com/protocol-buffers/docs/pythontutorial
protobuf是google推出的任意语言,任意平台,任意设备上皆可用的一种数据协议,具有以下特点:
- 使用
.proto
格式文件描述数据层级结构- protobuf编译器会根据
.proto
文件的描述自动创建一个class编码转换你指定的数据结构,同时生成的这类会自动提供数据转换的getter和setter方法,完成一个protocol buffer的内容的组成或者是读写- protobuf格式支持格式扩展兼容,使用旧的proto协议编码仍然可以读取使用了新协议编码的数据,当然你更新的新协议也是可以兼容之前proto的定义
一、定义proto协议
根据需要,定义一个proto文件:
1. 文件基础
-
.proto
文件最开始建议说明使用proto2还是proto3语法的声明:syntax = "proto2";
,如果不声明默认使用proto2的语法 -
.proto
文件必须以package xxxx;
声明开头,作为协议唯一的标识,避免不同项目的命名冲突 - 不过python本身在做关系调用的时候,是作为一种正常的索引结构调用的,也就相当于将编译后的以
_pb.py
结尾的文件认为是普通的python脚本这样用的,所以在proto中定义的package在编译过程中是没有用到package
的,但是避免其他语言使用的时候出现命名冲突,所以最好还是要在文件开头做好声明
2. message
- 一个
message
相当于一个指定类型的集合,像bool/int32/float/double/string
这些类型都是可以直接使用在proto协议中的某个message当中指定数据类型的 - 而且一个message可以直接嵌套另一个message使用,被嵌套的message就相当于string一样,被认为是一种数据类型,例如↓:
message Person{
required string name = 1;
message Education{
required int32 id = 1;
repeated string phonenumber = 2;
}
repeated Education eduinfo = 2;
}
3. message中的编号
- 上面的栗子可以看到,一个message里面标记的1/2/3这些数字,他们的作用是标识每种类型元素在当前二进制编码中的专属tag,不可以重复
- 做编码的时候1-15的tag数字标签比15+以后的tag数字少一个字节,所以在设计message的时候要尽可能的优化当前message数据结构,尽量是给常用的或者是使用过程中repeated比较多的数据使用1-15的tag标签,后续编码转换过程中可以保持最优状态
- 每个message的字段必须要声明是
required
||repeated
||optional
,需要关注的是,一旦定义字段为required
,后面就不能再修改,否则可能引起很多问题:
* 新编辑的proto内容,不可以修改已经存在的filed的tag值 && 不能添加和删除任何required
的filed && 新添加的filed要使用新的tag**,遵循以上原则才可以做到新旧版本message相互解析
* optional类型的filed最好在tag编号后面和;前面给一个默认值[default = value]
,当然可以不指定,不指定的时候默认为空
二、编译和使用proto协议
1. 编译protobuf内容为python所用
- python编译protobuf直接使用内部protobuf插件即可:
protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/your.proto
,这样生成的*_pb2.py
文件就可以直接用在python脚本中
2. protobuf的API
- python编译的
*_pb2.py
文件不会像Java和C++直接带有数据处理的代码,而是为所有的message
/enums
/fields
生成了特殊的解释器,同时生成了很多class - 有一种版本说: 对这一堆的class来说,几乎都有
__metaclass__ = reflection.GeneratedProtocolMessageType
这样的一行,这个可以理解成是一个固定标识,标识当前class;做加载的时候,GeneratedProtocolMessageType
这个方法会用特殊的解释器去创建python方法,来帮我们保证我们需要的每种message以及他们的关系 (然而我在使用过程中没有发现这个,可能版本有变化)
然后就可以用这样的class作为一个标准的作用域开始应用(就是python使用class这种方式)
在使用proto指定的数据结构的时候,必须严格对应.proto的定义
3. 数据类型的使用
-
Enums
:每个枚举对应有value值 -
Message
:每个message的class可能会包含下面的内容: -
IsInitialized()
:检查是否所有的required 内容都被赋值了 -
__str__()
:会返回一个可读的消息内容,在做debug的时候这个方法就非常有用的 -
CopyFrom(other_msg)
:复制一个message数据过来给,并做新的赋值 -
Clear()
:清空所有元素的value为空
4. proto数据解析和序列化:
- 每个protobuf的类都有读写messages的方法:
SerializeToString()
:序列化一个message返回一个string(需要注意的时候,message格式必须是二进制格式,而不是text格式)
ParseFromString(data)
:给一个string解析成一个序列化的message
三、举个栗子说明
栗子:
- 定义你的proto协议
person.proto
:
syntax = "proto2";
package infos;
message Person{
required int32 age=2;
required string name=3;
enum PhoneType{
MOBILE = 0;
WORK = 1;
HOME = 2;
}
message Score{
required string object=1;
optional int32 score = 2;
}
message PhoneNumber{
required int32 phone=1;
optional PhoneType type=2 [default = MOBILE];
}
repeated Score score=4;
optional PhoneNumber number=5;
}
message one{
required int32 id=1;
required Person people =2;
}
- 编译成python可用格式的
person_pb.py
:
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: person.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='person.proto',
package='infos',
syntax='proto2',
serialized_pb=_b('\n\x0cperson.proto\x12\x05infos\"\x94\x02\n\x06Person\x12\x0b\n\x03\x61ge\x18\x02 \x02(\x05\x12\x0c\n\x04name\x18\x03 \x02(\t\x12\"\n\x05score\x18\x04 \x03(\x0b\x32\x13.infos.Person.Score\x12)\n\x06number\x18\x05 \x01(\x0b\x32\x19.infos.Person.PhoneNumber\x1a&\n\x05Score\x12\x0e\n\x06object\x18\x01 \x02(\t\x12\r\n\x05score\x18\x02 \x01(\x05\x1aK\n\x0bPhoneNumber\x12\r\n\x05phone\x18\x01 \x02(\x05\x12-\n\x04type\x18\x02 \x01(\x0e\x32\x17.infos.Person.PhoneType:\x06MOBILE\"+\n\tPhoneType\x12\n\n\x06MOBILE\x10\x00\x12\x08\n\x04WORK\x10\x01\x12\x08\n\x04HOME\x10\x02\"0\n\x03one\x12\n\n\x02id\x18\x01 \x02(\x05\x12\x1d\n\x06people\x18\x02 \x02(\x0b\x32\r.infos.Person')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_PERSON_PHONETYPE = _descriptor.EnumDescriptor(
name='PhoneType',
full_name='infos.Person.PhoneType',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='MOBILE', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='WORK', index=1, number=1,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='HOME', index=2, number=2,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=257,
serialized_end=300,
)
_sym_db.RegisterEnumDescriptor(_PERSON_PHONETYPE)
_PERSON_SCORE = _descriptor.Descriptor(
name='Score',
full_name='infos.Person.Score',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='object', full_name='infos.Person.Score.object', index=0,
number=1, type=9, cpp_type=9, label=2,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='score', full_name='infos.Person.Score.score', index=1,
number=2, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=140,
serialized_end=178,
)
_PERSON_PHONENUMBER = _descriptor.Descriptor(
name='PhoneNumber',
full_name='infos.Person.PhoneNumber',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='phone', full_name='infos.Person.PhoneNumber.phone', index=0,
number=1, type=5, cpp_type=1, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='type', full_name='infos.Person.PhoneNumber.type', index=1,
number=2, type=14, cpp_type=8, label=1,
has_default_value=True, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=180,
serialized_end=255,
)
_PERSON = _descriptor.Descriptor(
name='Person',
full_name='infos.Person',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='age', full_name='infos.Person.age', index=0,
number=2, type=5, cpp_type=1, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='name', full_name='infos.Person.name', index=1,
number=3, type=9, cpp_type=9, label=2,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='score', full_name='infos.Person.score', index=2,
number=4, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='number', full_name='infos.Person.number', index=3,
number=5, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[_PERSON_SCORE, _PERSON_PHONENUMBER, ],
enum_types=[
_PERSON_PHONETYPE,
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=24,
serialized_end=300,
)
_ONE = _descriptor.Descriptor(
name='one',
full_name='infos.one',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='id', full_name='infos.one.id', index=0,
number=1, type=5, cpp_type=1, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='people', full_name='infos.one.people', index=1,
number=2, type=11, cpp_type=10, label=2,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=302,
serialized_end=350,
)
_PERSON_SCORE.containing_type = _PERSON
_PERSON_PHONENUMBER.fields_by_name['type'].enum_type = _PERSON_PHONETYPE
_PERSON_PHONENUMBER.containing_type = _PERSON
_PERSON.fields_by_name['score'].message_type = _PERSON_SCORE
_PERSON.fields_by_name['number'].message_type = _PERSON_PHONENUMBER
_PERSON_PHONETYPE.containing_type = _PERSON
_ONE.fields_by_name['people'].message_type = _PERSON
DESCRIPTOR.message_types_by_name['Person'] = _PERSON
DESCRIPTOR.message_types_by_name['one'] = _ONE
Person = _reflection.GeneratedProtocolMessageType('Person', (_message.Message,), dict(
Score = _reflection.GeneratedProtocolMessageType('Score', (_message.Message,), dict(
DESCRIPTOR = _PERSON_SCORE,
__module__ = 'person_pb2'
# @@protoc_insertion_point(class_scope:infos.Person.Score)
))
,
PhoneNumber = _reflection.GeneratedProtocolMessageType('PhoneNumber', (_message.Message,), dict(
DESCRIPTOR = _PERSON_PHONENUMBER,
__module__ = 'person_pb2'
# @@protoc_insertion_point(class_scope:infos.Person.PhoneNumber)
))
,
DESCRIPTOR = _PERSON,
__module__ = 'person_pb2'
# @@protoc_insertion_point(class_scope:infos.Person)
))
_sym_db.RegisterMessage(Person)
_sym_db.RegisterMessage(Person.Score)
_sym_db.RegisterMessage(Person.PhoneNumber)
one = _reflection.GeneratedProtocolMessageType('one', (_message.Message,), dict(
DESCRIPTOR = _ONE,
__module__ = 'person_pb2'
# @@protoc_insertion_point(class_scope:infos.one)
))
_sym_db.RegisterMessage(one)
# @@protoc_insertion_point(module_scope)
- 处理数据做协议转换的脚本
test_proto.py
:
#coding=utf-8
# file: test_proto.py
import testProto.person_pb2
def setInfo(a_info):
a_info.id = 1
a_person = a_info.people
a_person.age = 88
a_person.name = "first_name"
score1 = a_person.score.add()
score1.object = "python"
score1.score = 90
score2= a_person.score.add()
score2.object = "c++"
score2.score = 88
phone = a_person.number
phone.phone = 400100
phone.type = 2
# print(a_info)
return a_info
first_info = testProto.person_pb2.one()
one_info = setInfo(first_info)
print(one_info)
proto_info = one_info.SerializeToString()
print(proto_info)
def getInfo(wanted_info):
wanted_id = wanted_info.id
print("info id:", wanted_id)
print("his age: ", wanted_info.people.age)
for sco in wanted_info.people.score:
print("his scores:",sco)
print("his phoneType:", wanted_info.people.number.type)
if wanted_info.people.number.type == wanted_info.people.PhoneType.HOME:
print("his phonetype: HOME")
first_parsed = testProto.person_pb2.one()
first_parsed.ParseFromString(proto_info)
# print(first_parsed)
getInfo(first_parsed)
- 输出结果:
id: 1
people {
age: 88
name: "first_name"
score {
object: "python"
score: 90
}
score {
object: "c++"
score: 88
}
number {
phone: 400100
type: HOME
}
}
b'\x08\x01\x12+\x10X\x1a\nfirst_name"\n\n\x06python\x10Z"\x07\n\x03c++\x10X*\x06\x08\xe4\xb5\x18\x10\x02'
info id: 1
his age: 88
his scores: object: "python"
score: 90
his scores: object: "c++"
score: 88
his phoneType: 2
his phonetype: HOME
Process finished with exit code 0