使用protobuf主要是两个步骤,序列化和反序列化。html
关于Proto有哪些数据类型,而后如何编写,此处就不赘述了,百度一下有不少。python
此文主要是总结,python使用protobuf的过程,如何序列化和反序列化,对不一样类型的字段如何进行赋值。git
下面将一一列举各数据类型,在python中如何正确赋值。github
首先,得把编译包给导入web
import test_pb2 as pb
我分为两部分,分别为未被repeated
修饰的字段 和 被repeated
修饰后的字段json
test.proto
app
message SearchService { string type = 1; }
建立message对象,而后赋值便可。与python中,经过类建立实例,实例.属性
的方式进行赋值相似编辑器
search_service = pb.SearchService() search_service.type = "request"
test.proto
函数
message SearchService { int32 id = 2; }
与字符串赋值一致post
search_service = pb.SearchService() search_service.id = 1
test.proto
message SearchService { // 定义一个message类型 message SearchRequest { string content = 1; string keyword = 2; } // 类型 字段名 序号 SearchRequest searchRequest = 3; }
咱们看到在SearchService
里序号为3
的字段的类型为SearchRequest
,这是咱们新定义的message
若是把message看做是一个类,那么我将其实例化,而后赋值给对应的字段,能够吗?
ok,这是不行的,错误示例:
search_service = pb.SearchService() # 实例化SearchRequest search_request = pb.SearchService.SearchRequest() # 为search_request内部字段赋值 search_request.content = "hello protobuf" search_request.keyword = "mk" # 为search_service的searchRequest字段赋值 search_service.searchRequest = search_request
不容许在协议消息对象中分配复合字段“searchRequest”。
正确示例:
import test_pb2 as pb search_service.searchRequest.content = "hello protobuf!" search_service.searchRequest.keyword = "mk"
若是加上以前的那个字段,那么这样的:
import test_pb2 as pb search_service.type = "request" search_service.id = 1 search_service.searchRequest.content = "hello protobuf!" search_service.searchRequest.keyword = "mk"
枚举类型,注意一点:必须包含一个含0的字段
test.proto
syntax = "proto3"; message SearchService { enum SearchType { A = 0; B = 1; } SearchType searchType = 4; }
序号为4,类型为SearchType
的枚举类,名为searchType
的字段
此处的枚举类型,你能够看做是网页中的单选框,只能从给定的数据中选择一个,不选则默认为0
# 手动选择1 search_service.searchType = 1 # 或者是根据字段名进行选择 search_service.searchType = pb.SearchService.SearchType.A
test.proto
syntax = "proto3"; message SearchService { # 修饰符 类型 字段名 序号 repeated int32 uid = 5; }
uid
的类型是int32
,而后被repeated
修饰,即这个字段是可重复赋值的。
那么,在python中应该怎么赋值呢?
错误示例:
search_service.uid = 0
若是仍是和以前同样的赋值,就会报错
AttributeError: Assignment not allowed to repeated field "uid" in protocol message object.
正确示例:
search_service.uid.append(1) search_service.uid.append(2)
因此,你能够将被repeated
修饰的字段看做是一个空列表,往里添加值便可!
test.proto
syntax = "proto3"; message SearchService { message Second { string type = 1; string word = 2; } repeated Second seconds = 6; }
seconds
字段是可重复的message
类型,在python中该如何赋值?
# 实例化一个second second = search_service.Second() # 为second对象赋值 second.type = 'abc' second.word = 'world' # 添加至seconds列表中 search_service.seconds.append(second)
或者,你也能够这样
search_service.seconds.append( search_service.Second(type='efg', word="world") )
或者这样:
seconds = [ search_service.Second(type='1', word="world"), search_service.Second(type='2', word="world") ] search_service.seconds.extend(seconds)
因此,repeated
修饰的字段,在python中,就是一个列表
test.ptoto
syntax = "proto3"; message SearchService { enum SortOrder { key1 = 0; key2 = 1; key3 = 2; } repeated SortOrder sortOrder = 7; }
使用方法与以前的彻底一致
sortFields = [ # 此处key1 根据关键词,获取枚举值 search_service.SortOrder.key1, search_service.SortOrder.key2 ] search_service.sortOrder.extend(sortFields)
如今咱们已经所有赋值好了,接着就是序列化了
b = search_service.SerializeToString() print(b) # b'\n\x07request\x10\x01\x1a\x15\n\x0fhello protobuf!\x12\x02mk # \x02*\x02\x01\x022\x0c\n\x03abc\x12\x05world2\x0c\n\x03efg # \x12\x05world2\n\n\x011\x12\x05world2\n\n\x012\x12\x05world:\x02\x00\x01'
SerializeToString
Serializes the protocol message to a binary string.
序列化此协议消息为二进制串
如今,咱们是接收方,咱们收到了一串二进制。
首先,咱们须要使用将其反序列化,一样使用编译包。
search_service = pb.SearchService() b = b'\n\x07request\x10\x01\x1a\x15\n\x0fhello protobuf!\x12\x02mk \x02*\x02\x01\x022\x0c\n\x03abc\x12\x05world2\x0c\n\x03efg\x12\x05world2\n\n\x011\x12\x05world2\n\n\x012\x12\x05world:\x02\x00\x01' search_service.ParseFromString(b) # 访问属性值 print(search_service.type) # 输出:request
ParseFromString
解析函数
此时,search_service
就已经含有传输过来的所有数据了。若是你不想使用对象.属性
的方式调用,或者想使用相似json.loads
直接转为python中的字典,那么你可使用protobuf_to_dict
将其转为字典。
安装protobuf3_to_dict`
pip install protobuf3_to_dict
# 调用 from protobuf_to_dict import protobuf_to_dict b = b'\n\x07request\x10\x01\x1a\x15\n\x0fhello protobuf!\x12\x02mk \x02*\x02\x01\x022\x0c\n\x03abc\x12\x05world2\x0c\n\x03efg\x12\x05world2\n\n\x011\x12\x05world2\n\n\x012\x12\x05world:\x02\x00\x01' search_service.ParseFromString(b) # print(search_service.type) d = protobuf_to_dict(search_service) print(d, type(d)) # {'type': 'request', 'id': 1, 'searchRequest': {'content': 'hello protobuf!', 'keyword': 'mk'}, 'searchType': 2, 'uid': [1, 2], 'seconds': [{'type': 'abc', 'word': 'world'}, {'type': 'efg', 'word': 'world'}, {'type': '1', 'word': 'world'}, {'type': '2', 'word': 'world'}], 'sortOrder': [0, 1]} <class 'dict'>
本文中例子,我作了一个接口。
接口地址: http://47.101.154.110:8000/
请求头 | 请求体 | 请求方式 |
---|---|---|
必须指定Content-Type: application/grpc-web+proto | 序列化后的二进制 | POST |
你可使用postman
提交数据,来查看结果
也可使用Python发送请求
import requests headers = { 'Content-Type': 'application/grpc-web+proto' } b = b'\n\x07request\x10\x01\x1a\x15\n\x0fhello protobuf!\x12\x02mk \x02*\x02\x01\x022\x0c\n\x03abc\x12\x05world2\x0c\n\x03efg\x12\x05world2\n\n\x011\x12\x05world2\n\n\x012\x12\x05world:\x02\x00\x01' resp = requests.post('http://47.101.154.110:8000/', data=b, headers=headers) print(resp.text)
完整的test.proto
syntax = "proto3"; message SearchService { string type = 1; int32 id = 2; // 定义一个message类型 message SearchRequest { string content = 1; string keyword = 2; } // 类型 字段名 序号 SearchRequest searchRequest = 3; enum SearchType { A = 0; B = 1; } SearchType searchType = 4; repeated int32 uid = 5; message Second { string type = 1; string word = 2; } repeated Second seconds = 6; enum SortOrder { key1 = 0; key2 = 1; key3 = 2; } repeated SortOrder sortOrder = 7; }
完整的赋值示例
import test_pb2 as pb from protobuf_to_dict import protobuf_to_dict search_service = pb.SearchService() search_service.type = "request" search_service.id = 1 search_service.searchRequest.content = "hello protobuf!" search_service.searchRequest.keyword = "mk" # search_service.searchType = pb.SearchService.SearchType.A search_service.searchType = 2 search_service.uid.append(1) search_service.uid.append(2) second = search_service.Second() second.type = 'abc' second.word = 'world' search_service.seconds.append(second) search_service.seconds.append(search_service.Second(type='efg', word="world")) seconds = [ search_service.Second(type='1', word="world"), search_service.Second(type='2', word="world") ] search_service.seconds.extend(seconds) sortFields = [ search_service.SortOrder.key1, search_service.SortOrder.key2 ] search_service.sortOrder.extend(sortFields) b = search_service.SerializeToString() print(b)
在使用编译包时,没有代码提示,还有点不习惯。
这里,推荐安装mypy-protobuf
pip install mypy-protobuf
使用方法:
在你使用protoc
命令编译proto文件时,新增一个参数mypy-out=
,就像这样
protoc --python_out=. --mypy-out=. test.proto
此时会生成两个文件,并将他们拖入项目中的同一目录
test_pb2.py
:咱们须要导入使用的编译包
test_pb2.pyi
:存根文件,在编辑器中会有代码提示(想了解存根文件,能够看最下面的参考文章)
效果演示: