1. 程式人生 > >Python使用Protobuf&&如何賦值&&如何正反序列化

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

## 前言 使用protobuf主要是兩個步驟,**序列化和反序列化**。 關於Proto有哪些資料型別,然後如何編寫,此處就不贅述了,百度一下有很多。 此文主要是總結,python使用protobuf的過程,如何序列化和反序列化,對不同型別的欄位如何進行賦值。 ## 序列化 下面將一一列舉各資料型別,在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 ``` ![image-20210116211914264](http://cdn.z2blog.com/20210116211914.png) > 不允許在協議訊息物件中分配複合字段“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的欄位** ![image-20210116212524288](http://cdn.z2blog.com/20210116212524.png) `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]} ``` ## 小小嚐試 本文中例子,我做了一個介面。 介面地址: `http://47.101.154.110:8000/` | 請求頭 | 請求體 | 請求方式 | | ------------------------------------------------ | ---------------- | -------- | | 必須指定Content-Type: application/grpc-web+proto | 序列化後的二進位制 | POST | 你可以使用`postman`提交資料,來檢視結果 ![456](http://cdn.z2blog.com/20210117122125.gif) 也可以使用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`:存根檔案,在編輯器中會有程式碼提示(想了解存根檔案,可以看最下面的參考文章) 效果演示: ![演示](http://cdn.z2blog.com/20210116231422.gif) ## 參考文章 https://github.com/dropbox/mypy-protobuf [pyi檔案是幹嘛的?(一文讀懂Python的存根檔案和型別檢查)](https://www.cnblogs.com/chester-cs/p/140009