程序地带

Python使用Protobuf&&如何赋值&&如何正反序列化


前言

使用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

image-20210116211914264


不允许在协议消息对象中分配复合字段“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的字段


image-20210116212524288


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" x07requestx10x01x1ax15 x0fhello protobuf!x12x02mk
# x02*x02x01x022x0c x03abcx12x05world2x0c x03efg
# x12x05world2 x011x12x05world2 x012x12x05world:x02x00x01"

SerializeToString


Serializes the protocol message to a binary string.


序列化此协议消息为二进制串


反序列化

现在,我们是接收方,我们收到了一串二进制。


首先,我们需要使用将其反序列化,同样使用编译包。


search_service = pb.SearchService()
b = b" x07requestx10x01x1ax15 x0fhello protobuf!x12x02mk x02*x02x01x022x0c x03abcx12x05world2x0c x03efgx12x05world2 x011x12x05world2 x012x12x05world:x02x00x01"
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" x07requestx10x01x1ax15 x0fhello protobuf!x12x02mk x02*x02x01x022x0c x03abcx12x05world2x0c x03efgx12x05world2 x011x12x05world2 x012x12x05world:x02x00x01"
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提交数据,来查看结果


456


也可以使用Python发送请求


import requests
headers = {
"Content-Type": "application/grpc-web+proto"
}
b = b" x07requestx10x01x1ax15 x0fhello protobuf!x12x02mk x02*x02x01x022x0c x03abcx12x05world2x0c x03efgx12x05world2 x011x12x05world2 x012x12x05world:x02x00x01"
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的存根文件和类型检查)


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/mkdir700/p/14290346.html

随机推荐

Kafka记录

Kafka记录

官网: http://kafka.apache.org/Kafka架构:Kafka使用ZooKeeper作为其分布式协调框架,其动态扩容是通过ZooKeeper来实现的。Kafka使用Z...

Beth_Chan 阅读(181)

5分钟搭建好ElasticSearch开发环境

5分钟搭建好ElasticSearch开发环境

5分钟搭建好ElasticSearch开发环境基于ElasticSearch7ElasticSearch简介Elaticsearch,简称为es,es是一个开源的高扩展的分...

代码狂魔v 阅读(807)

hashmap 复制_嘘!小声哔哔HashMap,不然打你哦

hashmap 复制_嘘!小声哔哔HashMap,不然打你哦

本人作为掘金社区的一个小萌新,在翻看各路大神写的技术博客时,佩服其知识储备之丰富,羡慕其文笔之潇洒,于是萌生了俺也试试的想法。鉴于初次撰笔&#x...

whiteillusions 阅读(941)

微信小程序报错

微信小程序报错

一"errMsg":"request:failinvalidurl"undefined/productCart/。。。。。。""}是因为微信请求地址没...

大白菜1号 阅读(344)

量子通信秘钥分发

一点小总结,是看了抖音"科学探知"讲的量子秘钥分发原理,记录一下。在信息传输的过程中一般是通过一些加密算法产生秘钥,将秘钥和加密后的信息一同传...

冥又 阅读(283)