在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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容