使用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
:存根文件,在編輯器中會有代碼提示(想了解存根文件,能夠看最下面的參考文章)
效果演示: