在python中使用protobuf

官方:https://developers.google.com/protocol-buffers/docs/pythontutorial

protobuf是google推出的任意语言,任意平台,任意设备上皆可用的一种数据协议,具有以下特点:

  1. 使用.proto格式文件描述数据层级结构
  2. protobuf编译器会根据.proto文件的描述自动创建一个class编码转换你指定的数据结构,同时生成的这类会自动提供数据转换的getter和setter方法,完成一个protocol buffer的内容的组成或者是读写
  3. 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

你可能感兴趣的:(在python中使用protobuf)