ProtoBuf

protobuf 在python中的使用

一. 介绍

官方介绍:

1
2
3
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。  
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

简单来讲, ProtoBuf 是结构数据序列化方法,可简单类比于 XML,其具有以下特点:

  1. 语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
  2. 高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
  3. 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序

二. 使用

ProtoBuf的核心文件是后缀名为”.proto”的文件,这个文件里定义了通信的数据的格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//指定protobuf语法版本
syntax = "proto2";

//包名
option java_package = "com.lhc.protobuf";
//源文件类名
option java_outer_classname = "AddressBookProtos";

// class Person
message Person {
//required 必须设置(不能为null)
required string name = 1;
//int32 对应java中的int
required int32 id = 2;
//optional 可以为空
optional string email = 3;

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

message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
//repeated 重复的 (集合)
repeated PhoneNumber phones = 4;
}

message AddressBook {
repeated Person people = 1;
}

官方文档:https://developers.google.com/protocol-buffers/docs/proto

安装

下载protoc:https://github.com/google/protobuf/releases
解压后就可使用
为了方便使用,可以将bin文件夹加入到环境变量中,使用protoc --version可以检测是否配置成功

生成python代码

当拿到.proto文件,使用以下命令生成python代码:

1
protoc --python_output=./ *.proto

--python_output指明生成文件的路径
*.proto即待转化的proto协议文件

将PB转化成需要的数据

  1. 接收到的pb字符串转化为目标对象
  2. 将对象转化成pb字符串
  3. 将字典类型的数据转化成pb对象
  4. 将pb对象转化成字典
  5. 将json类型的数据转化成pb对象
  6. 将pb对象转化成json

以下是完成的转化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import simplejson
from google.protobuf.descriptor import FieldDescriptor as FD
from core.management.utils.test_pb2 import FrameData

def pb_str2object(cls, message):
"""
将接收到的protobuf string转化成可解析的数据
:type cls: 目标对象
:type message: 带转化内容
"""
return cls.ParseFromString(message)


def object2pb_str(object_pb):
"""
将object_pb转化为protobuf string返回
:type object_pb: 需要转化成pb string的对象
"""
return object_pb.SerializeToString()


class ConvertException(Exception):
pass


def dict2object(cls, a_dict, strict=False):
"""
Takes a class representing the ProtoBuf Message and fills it with data from
the dict.
:type cls: 目标对象
:type a_dict: 带转化dict
:type strict:
"""
obj = cls()
for field in obj.DESCRIPTOR.fields:
if not field.label == field.LABEL_REQUIRED:
continue
if not field.has_default_value:
continue
if field.name not in a_dict:
raise ConvertException('Field "%s" missing from descriptor dictionary.'
% field.name)
field_names = set([field.name for field in obj.DESCRIPTOR.fields])
if strict:
for key in a_dict.keys():
if key not in field_names:
raise ConvertException(
'Key "%s" can not be mapped to field in %s class.'
% (key, type(obj)))
for field in obj.DESCRIPTOR.fields:
if field.name not in a_dict:
continue
msg_type = field.message_type
if field.label == FD.LABEL_REPEATED:
if field.type == FD.TYPE_MESSAGE:
for sub_dict in a_dict[field.name]:
item = getattr(obj, field.name).add()
item.CopyFrom(dict2object(msg_type._concrete_class, sub_dict))
else:
map(getattr(obj, field.name).append, a_dict[field.name])
else:
if field.type == FD.TYPE_MESSAGE:
value = dict2object(msg_type._concrete_class, a_dict[field.name])
getattr(obj, field.name).CopyFrom(value)
else:
setattr(obj, field.name, a_dict[field.name])
return obj


def object2dict(obj):
"""
Takes a ProtoBuf Message obj and converts it to a dict.
:type obj: 待转化对象
"""
adict = {}
if not obj.IsInitialized():
return None
for field in obj.DESCRIPTOR.fields:
if not getattr(obj, field.name):
continue
if not field.label == FD.LABEL_REPEATED:
if not field.type == FD.TYPE_MESSAGE:
adict[field.name] = getattr(obj, field.name)
else:
value = object2dict(getattr(obj, field.name))
if value:
adict[field.name] = value
else:
if field.type == FD.TYPE_MESSAGE:
adict[field.name] = \
[object2dict(v) for v in getattr(obj, field.name)]
else:
adict[field.name] = [v for v in getattr(obj, field.name)]
return adict


def json2object(cls, json, strict=False):
"""
Takes a class representing the Protobuf Message and fills it with data from
the json string.
:type cls: 目标对象
:type json: 待转化json
:type strict:
"""
return dict2object(cls, simplejson.loads(json), strict)


def object2json(obj):
"""
Takes a ProtoBuf Message obj and converts it to a json string.
:type obj: 待转化对象
"""
return simplejson.dumps(object2dict(obj), sort_keys=True, indent=4)

参考:https://github.com/NextTuesday/py-pb-converters/blob/master/pbjson.py

------------- The End -------------