前言
使用protobuf主要是两个步骤,序列化和反序列化。
关于Proto有哪些数据类型,然后如何编写,此处就不赘述了,百度一下有很多。
此文主要是总结,python使用protobuf的过程,如何序列化和反序列化,对不同类型的字段如何进行赋值。
序列化
下面将一一列举各数据类型,在python中如何正确赋值。
首先,得把编译包给导入
import test_pb2 as pb
我分为两部分,分别为未被repeated
修饰的字段 和 被repeated
修饰后的字段
无修饰符
字符串
test.proto
message SearchService {
string type = 1;
}
创建message对象,然后赋值即可。与python中,通过类创建实例,实例.属性
的方式进行赋值类似
search_service = pb.SearchService()
search_service.type = "request"
数字型
test.proto
message SearchService {
int32 id = 2;
}
与字符串赋值一致
search_service = pb.SearchService()
search_service.id = 1
Message
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"
Enum
枚举类型,注意一点:必须包含一个含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
被repeated修饰的字段
字符串或数字
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
修饰的字段看作是一个空列表,往里添加值即可!
Message
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中,就是一个列表
Enum
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
:存根文件,在编辑器中会有代码提示(想了解存根文件,可以看最下面的参考文章)
效果演示:
参考文章
https://github.com/dropbox/mypy-protobuf
pyi文件是干嘛的?(一文读懂Python的存根文件和类型检查)