python基础--protobuf的使用(一)

protobuf2/3语法介绍

Protobuf(Google Protocol Buffers)是google开发的的一套用于数据存储,网络通信时用于协议编解码的工具库.它和XML和Json数据差不多,把数据已某种形式保存起来.Protobuf相对与XML和Json的不同之处,它是一种二进制的数据格式,具有更高的传输,打包和解包效率

参考资料:
https://github.com/google/protobuf
以下的需要VPN翻墙
pythontutorial
https://developers.google.com/protocol-buffers/docs/pythontutorial

python-generated
https://developers.google.com/protocol-buffers/docs/reference/python-generated#extension

语言指南
https://developers.google.com/protocol-buffers/docs/proto3
API文档
https://developers.google.com/protocol-buffers/docs/reference/python/?hl=zh-cn

protobuf2语法介绍

Protobuf的语法
*.proto文件中数据类型可以分为两大类:
复合数据类型包括:枚举和message类型
标准数据类型包含:整型,浮点,字符串等

数据类型前面修饰词:
①required: 必须赋值,不能为空,否则该条message会被认为是“uninitialized”。除此之外,“required”字段跟“optional”字段并无差别。

②optional:字段可以赋值,也可以不赋值。假如没有赋值的话,会被赋上默认值。

③repeated: 该字段可以重复任意次数,包括0次。重复数据的顺序将会保存在protocol buffer中,将这个字段想象成一个可以自动设置size的数组就可以了。

每个字段要给数字
该Number是用来标记该字段在序列化后的二进制数据中所在的field,每个字段的Number在message内部都是独一无二的。也不能进行改变,否则数据就不能正确的解包

protobuf3语法介绍

1.字段前取消了required和optional两个关键字,目前可用的只有repeated关键字。
2.不可以现设置默认值了。
  a.string默认为空串
  b.枚举默认为第一个枚举定义的第一个值。并且必须是0,必须有有一个0值,我们可以用这个0值作为默认值。
这个零值必须为第一个元素,为了兼容proto2语义,枚举类的第一个值总是默认值。
  c.bytes默认为空bytes
  d.bool默认为false
  e.数字类型默认为0
3.protoType类型如下:
double、float、int32、int64、uint32、uint64、sint32、sint64、fixed32、fixed64、sfixed32、sfixed64、bool、string、bytes

4、分配标识号
正如你所见,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。

指定字段规则
所指定的消息字段修饰符必须是如下之一:
singular:一个格式良好的消息应该有0个或者1个这种字段(但是不能超过1个)。
repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。
在proto3中,repeated的标量域默认情况虾使用packed。

安装

参考GitHub:https://github.com/google/protobuf

linux安装

  1. 下载protobuf源代码(当前最新版本为:3.6.0)
    下载地址https://github.com/google/protobuf/releases
    cd /opt
    wget https://github.com/google/protobuf/releases/download/v3.6.0/protobuf-python-3.6.0.tar.gz

  2. 解压,编译,安装
    tar zxvf protobuf-python-3.6.0.tar.gz
    cd protobuf-3.6.0
    ./configure
    make
    make check
    make install

  3. 继续安装protobuf的python模块(如果不用python,可跳过这一步)
    cd ./python
    python setup.py build
    python setup.py test
    python setup.py install

  4. 安装完成,验证Linux命令
    protoc –version

  5. 验证Python模块是否被正确安装
    python

    import google.protobuf
    如果没有报错,说明安装正常。

Mac安装

通过brew install protobuf安装即可
然后在安装protobuf需要的依赖brew install autoconf automake libtool
以下是测试效果:

zhiliaodeMBP:3.6.0 zhiliao$ protoc --version
libprotoc 3.6.0
zhiliaodeMBP:3.6.0 zhiliao$ python
Python 3.6.5 |Anaconda, Inc.| (default, Apr 26 2018, 08:42:37) 
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import google.protobuf
>>> 

一个完整的例子

先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

文件的第一行指定了你正在使用proto3语法:如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。
SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。

接下来我们看一个整体的例子吧,以下是demo的结构
这里写图片描述

1、第一步,编写addressbook.proto文件,注意以.proto结尾

syntax = "proto3";
package tutorial;

message AddressBook {
  repeated Person people = 1;
}

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
  float money = 4;
  bool work_status = 5;

  repeated PhoneNumber phones = 6;
  MyMessage maps = 7;

}

message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
}

enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
}


message MyMessage {
  map<int32, int32> mapfield = 1;
}

1、第二步,编译addressbook.proto文件

zhiliaodeMBP:protobuf_python zhiliao$ protoc ./addressbook.proto  --python_out=./
zhiliaodeMBP:protobuf_python zhiliao$ pwd
/Users/zhiliao/zhiliao/untitled1/protobuf_python
zhiliaodeMBP:protobuf_python zhiliao$ 

编译完毕,会自动生成addressbook_pb2.py文件
另外protoc ./addressbook.proto –python_out=./这是输入的命令行指令,意思是在当前目录输出默认的即可,也即是截图里面的addressbook_pb2.py

3、第三步,编译.py文件,进行序列化和凡序列化
add_person.py

#! /usr/bin/env python
from tutorial import addressbook_pb2

address_book = addressbook_pb2.AddressBook()
person = address_book.people.add()

person.id = 1
person.name = "safly"
person.email = "safly@qq.com"
person.money = 1000.11
person.work_status = True

phone_number = person.phones.add()
phone_number.number = "123456"
phone_number.type = addressbook_pb2.MOBILE

maps = person.maps
maps.mapfield[1] = 1
maps.mapfield[2] = 2

#序列化
serializeToString = address_book.SerializeToString()
print(serializeToString,type(serializeToString))



address_book.ParseFromString(serializeToString)

for person in address_book.people:
  print("p_id{},p_name{},p_email{},p_money{},p_workstatu{}"
        .format(person.id,person.name,person.email,person.money,person.work_status))

  for phone_number in person.phones:
    print(phone_number.number,phone_number.type)


  for key in person.maps.mapfield:
    print(key,person.maps.mapfield[key])



编译该py文件,输出结果如下:

b'\n6\n\x05safly\x10\x01\x1a\x0csafly@qq.com%\n\x07zD(\x012\x08\n\x06123456:\x0c\n\x04\x08\x01\x10\x01\n\x04\x08\x02\x10\x02' <class 'bytes'>

p_id1,p_namesafly,p_emailsafly@qq.com,p_money1000.1099853515625,p_workstatuTrue
123456 0
1 1
2 2

我们就看到了序列化和反序列化的结果

今天就简单写个例子,后续的博客,会细致的分析各个数据类型,以及如何在web前端进行解码交互等

一个完整的例子(进阶版)

我们在对上面那个例子加入引入外部proto、map、嵌套
这里写图片描述

addressbook.proto内容如下:

syntax = "proto3";
package tutorial;
import "emu.proto";


message AddressBook {
  repeated Person people = 1;
}

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
  float money = 4;
  bool work_status = 5;

  repeated PhoneNumber phones = 6;
  repeated MyMessage maps = 7;

  //内部嵌套
  repeated Hobby hobby = 8;
  message Hobby{
    string interest = 1;
  }

}

message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
}

enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
}

我们将addressbook.proto中的repeated MyMessage maps = 7;进行了外部引用,
emu.proto如下:


syntax = "proto3";
package tutorial;


message MyMessage {
  map<int32, int32> mapfield = 1;
}

然后首先对emu.proto进行编译,
protoc ./emu.proto --python_out=./

然后会addressbook.proto进行编译
protoc ./addressbook.proto --python_out=./
然后会默认生成上述截图中的emu_pb2.py、addressbook_pb2.py文件

我们接下来看看add_person.py代码

#! /usr/bin/env python
from tutorial import addressbook_pb2

address_book = addressbook_pb2.AddressBook()
person = address_book.people.add()

person.id = 1
person.name = "safly"
person.email = "safly@qq.com"
person.money = 1000.11

person.work_status = True

phone_number = person.phones.add()
phone_number.number = "123456"
phone_number.type = addressbook_pb2.MOBILE

maps = person.maps.add()
maps.mapfield[1] = 1
maps.mapfield[2] = 2

hobby = person.hobby.add()
hobby.interest = "python"


#序列化
serializeToString = address_book.SerializeToString()
print(serializeToString,type(serializeToString))



address_book.ParseFromString(serializeToString)

for person in address_book.people:
  print("p_id{},p_name{},p_email{},p_money{},p_workstatu{}"
        .format(person.id,person.name,person.email,person.money,person.work_status))

  for phone_number in person.phones:
    print(phone_number.number,phone_number.type)
  print(person.phones[0].number)


  for map in person.maps:
    for key in map.mapfield:
      print(key,'-------',map.mapfield[key])


  for hobby in person.hobby:
    print(hobby.interest)

最后输出结果如下:

/Users/zhiliao/miniconda3/bin/python /Users/zhiliao/zhiliao/untitled1/tutorial/add_person.py
b'\n@\n\x05safly\x10\x01\x1a\x0csafly@qq.com%\n\x07zD(\x012\x08\n\x06123456:\x0c\n\x04\x08\x01\x10\x01\n\x04\x08\x02\x10\x02B\x08\n\x06python' <class 'bytes'>
p_id1,p_namesafly,p_emailsafly@qq.com,p_money1000.1099853515625,p_workstatuTrue
123456 0
123456
2 ------- 2
1 ------- 1
python

Process finished with exit code 0
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页