美文网首页
在python中使用protobuf

在python中使用protobuf

作者: 何妨吟啸且徐行1 | 来源:发表于2019-11-18 22:19 被阅读0次

    官方: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

          本文链接:https://www.haomeiwen.com/subject/ngqsictx.html