Loading... ## 前言 使用protobuf主要是两个步骤,**序列化和反序列化**。 关于Proto有哪些数据类型,然后如何编写,此处就不赘述了,百度一下有很多。 此文主要是总结,python使用protobuf的过程,如何序列化和反序列化,对不同类型的字段如何进行赋值。 在本文最后,给出了一个本文的示例接口,你可以试试请求接口,体验一下`gRPC`的传输方式。 ## 序列化 下面将一一列举各数据类型,在python中如何正确赋值,赋值后如何序列化。 首先,得把编译包给导入 ``` import test_pb2 as pb ``` **我分为两部分,分别为未被`repeated`修饰的字段 和 被`repeated`修饰后的字段** ### 无修饰符 #### 字符串 `test.proto` ```protobuf message SearchService { string type = 1; } ``` 创建message对象,然后赋值即可。与python中,通过类创建实例,`实例.属性`的方式进行赋值类似 ```python search_service = pb.SearchService() search_service.type = "request" ``` #### 数字型 `test.proto` ```protobuf message SearchService { int32 id = 2; } ``` 与字符串赋值一致 ```python search_service = pb.SearchService() search_service.id = 1 ``` #### Message `test.proto` ```protobuf message SearchService { // 定义一个message类型 message SearchRequest { string content = 1; string keyword = 2; } // 类型 字段名 序号 SearchRequest searchRequest = 3; } ``` 我们看到在`SearchService`里序号为`3`的字段的类型为`SearchRequest`,这是我们新定义的message 如果把message看作是一个类,那么我将其实例化,然后赋值给对应的字段,可以吗? ok,这是不行的,错误示例: ```python 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”。 正确示例: ```python import test_pb2 as pb search_service.searchRequest.content = "hello protobuf!" search_service.searchRequest.keyword = "mk" ``` 如果加上之前的那个字段,那么这样的: ```python 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` ```protobuf syntax = "proto3"; message SearchService { enum SearchType { A = 0; B = 1; } SearchType searchType = 4; } ``` 序号为4,类型为`SearchType`的枚举类,名为`searchType`的字段 此处的枚举类型,你可以看作是网页中的单选框,只能从给定的数据中选择一个,不选则默认为0 ```python # 手动选择1 search_service.searchType = 1 # 或者是根据字段名进行选择 search_service.searchType = pb.SearchService.SearchType.A ``` ### 被repeated修饰的字段 #### 字符串或数字 `test.proto` ```protobuf syntax = "proto3"; message SearchService { # 修饰符 类型 字段名 序号 repeated int32 uid = 5; } ``` `uid`的类型是`int32`,然后被`repeated`修饰,即这个字段是可重复赋值的。 那么,在python中应该怎么赋值呢? 错误示例: ```python search_service.uid = 0 ``` 如果还是和之前一样的赋值,就会报错 > AttributeError: Assignment not allowed to repeated field "uid" in protocol message object. 正确示例: ```python search_service.uid.append(1) search_service.uid.append(2) ``` 所以,你可以将被`repeated`修饰的字段看作是一个空列表,往里添加值即可! #### Message `test.proto` ```protobuf syntax = "proto3"; message SearchService { message Second { string type = 1; string word = 2; } repeated Second seconds = 6; } ``` `seconds`字段是可重复的`message`类型,在python中该如何赋值? ```python # 实例化一个second second = search_service.Second() # 为second对象赋值 second.type = 'abc' second.word = 'world' # 添加至seconds列表中 search_service.seconds.append(second) ``` 或者,你也可以这样 ```python search_service.seconds.append( search_service.Second(type='efg', word="world") ) ``` 或者这样: ```python 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` ```protobuf syntax = "proto3"; message SearchService { enum SortOrder { key1 = 0; key2 = 1; key3 = 2; } repeated SortOrder sortOrder = 7; } ``` 使用方法与之前的完全一致 ```python sortFields = [ # 此处key1 根据关键词,获取枚举值 search_service.SortOrder.key1, search_service.SortOrder.key2 ] search_service.sortOrder.extend(sortFields) ``` 现在我们已经全部赋值好了,接着就是序列化了 ```python 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. > > 序列化此协议消息为二进制串 ## 反序列化 现在,我们是接收方,我们收到了一串二进制。 首先,我们需要使用将其反序列化,同样使用编译包。 ```python 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 ``` ```python # 调用 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发送请求 ```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` ```protobuf 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; } ``` 完整的赋值示例 ```python 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](https://github.com/dropbox/mypy-protobuf) [pyi文件是干嘛的?(一文读懂Python的存根文件和类型检查)](https://www.cnblogs.com/chester-cs/p/14000921.html) Last modification:February 18th, 2021 at 08:12 pm © 允许规范转载