本篇主要介紹如何在Python語言中使用Google Protocol Buffer(后續都簡寫為PB), 包括以下幾個部分:
- 為什么要使用PB?
- 安裝Google PB
- 自定義.proto 檔案
- 編譯.proto檔案
- 決議目標py檔案
- 序列化和反序列化
- 更復雜的Message
- 動態編譯
為什么要使用PB?
PB(Protocol Buffer)是 Google 開發的用于結構化資料交換格式,作為騰訊云日志服務標準寫入格式,因此用于寫入日志資料前,需要將日志原始資料序列化為 PB 資料流后通過 API 寫入服務端,而各個端類程式中不便操作PB格式,因此需要在端類和日志服務之間加入一層PB轉化層,
當然PB格式也有自己的優點,主要是簡單和快,具體測驗結果參見Google序列化基準分析
安裝Google PB
如果要想在Python中使用PB,需要先安裝PB編譯器protoc去編譯你的.proto檔案,安裝方法如下:
下載最新的protobuf release包安裝即可,當前版本為3.5.1,安裝步驟如下
wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.tar.gz
tar xvfz protobuf-all-3.5.1.tar.gz
cd protobuf-3.5.1/
./configure --prefix=/usr
make
make check
make install
check步驟全部通過即表示編譯通過,
繼續安裝protobuf的python模塊
cd ./python
python setup.py build
python setup.py test
python setup.py install
安裝完成驗證protoc命令
root@ubuntu:~# protoc --version
libprotoc 3.5.1
protobuf的默認安裝位置是 /usr/local,/usr/local/lib 不在Ubuntu系統默認的 LD_LIBRARY_PATH 里,如果在Ubuntu系統中configure時未指定安裝路徑為/usr, 則會出現以下錯誤
protoc: error while loading shared libraries: libprotoc.so.8: cannot open shared object file: No such file or directory
可以使用ldconfig命令解決,參考Protobuf cannot find shared libraries,這個錯誤在安裝包的README中有提到,當然重新安裝也可以
驗證Python模塊是否被正確安裝
import google.protobuf
在python解釋器中如果上面的import沒有報錯,說明安裝正常,
自定義.proto 檔案
首先我們需要撰寫一個 proto 檔案,定義我們程式中需要處理的結構化資料,在 protobuf 的術語中,結構化資料被稱為 Message,proto 檔案非常類似 java 或者 C++ 語言的資料定義,proto示例檔案cls.Log.proto如下:
syntax = "proto2";
package cls;
message Log
{
optional uint64 time = 1; // UNIX Time Format
required string topic_id = 2;
required string content = 3;
}
.proto檔案開頭是包的宣告,為了幫助防止在不同的工程中命名沖突,在Python中,包通常由目錄結構決定的,所以這個.proto檔案定義的包,在實際Python代碼中是沒有效果的,但是,按照官方的建議是堅持宣告這條陳述句,主要作用是為了在PB的命名空間中防止名稱沖突,package 名字叫做 cls,定義了一個訊息 Log,該訊息有三個成員,各個成員的含義如下:
| 欄位名 | 型別 | 位置 | 是否必須 | 含義 |
|---|---|---|---|---|
| time | uint64 | body | 否 | 日志時間,不指定,則使用服務器收到請求的時間 |
| topic_id | string | body | 是 | 日志上報到的日志主題id |
| content | string | body | 是 | 日志內容 |
一個比較好的習慣是認真對待 proto 檔案的檔案名,比如將命名規則定為: packageName.MessageName.proto
編譯.proto檔案
使用編譯器protoc直接編譯即可,需要指定源檔案路徑和目標檔案路徑
SRC_DIR=/tmp/src_dir
DST_DIR=/tmp/dst_dir
protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/cls.Log.proto
生成Python類就使用--python_out選項,如果要生成C++類時使用--cpp_out選項
決議目標py檔案
在目標檔案夾中生成的檔案目錄對應如下:
root@ubuntu:/tmp/dst_dir# tree
.
└── cls
└── Log_pb2.py
1 directory, 1 file
其中Log_pb2.py檔案的內容如下(不允許編輯):
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: cls.Log.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='cls.Log.proto',
package='cls',
syntax='proto2',
serialized_pb=_b('\n\rcls.Log.proto\x12\x03\x63ls\"6\n\x03Log\x12\x0c\n\x04time\x18\x01 \x01(\x04\x12\x10\n\x08topic_id\x18\x02 \x02(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x02(\t')
)
_LOG = _descriptor.Descriptor(
name='Log',
full_name='cls.Log',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='time', full_name='cls.Log.time', index=0,
number=1, type=4, cpp_type=4, label=1,
has_default_value=https://www.cnblogs.com/CHLL55/p/False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='topic_id', full_name='cls.Log.topic_id', index=1,
number=2, type=9, cpp_type=9, label=2,
has_default_value=https://www.cnblogs.com/CHLL55/p/False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='content', full_name='cls.Log.content', index=2,
number=3, type=9, cpp_type=9, label=2,
has_default_value=https://www.cnblogs.com/CHLL55/p/False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=22,
serialized_end=76,
)
DESCRIPTOR.message_types_by_name['Log'] = _LOG
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Log = _reflection.GeneratedProtocolMessageType('Log', (_message.Message,), dict(
DESCRIPTOR = _LOG,
__module__ = 'cls.Log_pb2'
# @@protoc_insertion_point(class_scope:cls.Log)
))
_sym_db.RegisterMessage(Log)
# @@protoc_insertion_point(module_scope)
關于pb生成的py檔案的源代碼的決議暫時擱置,可以參見附件中的資料
序列化和反序列化
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on 1/30/18 4:23 PM
@author: Chen Liang
@function: pb test
"""
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import Log_pb2
import json
def serialize_to_string(msg_obj):
ret_str = msg_obj.SerializeToString()
return ret_str
def parse_from_string(s):
log = Log_pb2.Log()
log.ParseFromString(s)
return log
if __name__ == '__main__':
# serialize_to_string
content_dict = {"live_id": "1239182389648923", "identify": "zxc_unique"}
tencent_log = Log_pb2.Log()
tencent_log.time = 1510109254
tencent_log.topic_id = "John Doe"
tencent_log.content = json.dumps(content_dict)
ret_s = serialize_to_string(tencent_log)
print(type(ret_s))
print(ret_s)
# parse_from_string
log_obj = parse_from_string(ret_s)
print(log_obj)
其中關鍵的操作在于message物件的寫入和讀取以及序列化函式SerializeToString和反序列化函式ParseFromString
更復雜的Message
到這里為止,我們只給出了一個簡單的上傳日志的例子,在實際應用中,人們往往需要定義更加復雜的 Message,我們用“復雜”這個詞,不僅僅是指從個數上說有更多的 fields 或者更多型別的 fields,而是指更加復雜的資料結構:
- Message嵌套
- Import Message
下面分別介紹
Message嵌套
嵌套是一個神奇的概念,一旦擁有嵌套能力,訊息的表達能力就會非常強大,具體的嵌套 Message 的例子如下
message Person {
required string name = 1;
required int32 id = 2; // Unique ID number for this person.
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
在 Message Person 中,定義了嵌套訊息 PhoneNumber,并用來定義 Person 訊息中的 phone 域,這使得人們可以定義更加復雜的資料結構,
Import Message
在一個 .proto 檔案中,還可以用 Import 關鍵字引入在其他 .proto 檔案中定義的訊息,這可以稱做 Import Message,或者 Dependency Message,具體的import message的例子如下
import common.header;
message youMsg{
required common.info_header header = 1;
required string youPrivateData = https://www.cnblogs.com/CHLL55/p/2;
}
其中 ,common.info_header定義在common.header包內,
Import Message 的用處主要在于提供了方便的代碼管理機制,類似 C 語言中的頭檔案,您可以將一些公用的 Message 定義在一個 package 中,然后在別的 .proto 檔案中引入該 package,進而使用其中的訊息定義,
Google Protocol Buffer 可以很好地支持嵌套 Message 和引入 Message,從而讓定義復雜的資料結構的作業變得非常輕松愉快,
動態編譯
一般情況下,使用 Protobuf 的人們都會先寫好 .proto 檔案,再用 Protobuf 編譯器生成目標語言所需要的源代碼檔案,將這些生成的代碼和應用程式一起編譯,
可是在某且情況下,人們無法預先知道 .proto 檔案,他們需要動態處理一些未知的 .proto 檔案,比如一個通用的訊息轉發中間件,它不可能預知需要處理怎樣的訊息,這需要動態編譯 .proto 檔案,并使用其中的 Message,
詳細解釋參見:Google Protocol Buffer 的使用和原理
參考:
- https://developers.google.com/protocol-buffers/docs/reference/python/
- https://developers.google.com/protocol-buffers/docs/reference/python-generated
- http://hzy3774.iteye.com/blog/2323428
- https://github.com/google/protobuf/tree/master/python
- https://github.com/google/protobuf/tree/master/examples
- https://blog.csdn.net/losophy/article/details/17006573
- https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/
- https://github.com/google/protobuf
- https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.tar.gz
- Python Google Protocol Buffer: https://developers.google.com/protocol-buffers/docs/pythontutorial
記得幫我點贊哦!
精心整理了計算機各個方向的從入門、進階、實戰的視頻課程和電子書,按照目錄合理分類,總能找到你需要的學習資料,還在等什么?快去關注下載吧!!!

念念不忘,必有回響,小伙伴們幫我點個贊吧,非常感謝,
我是職場亮哥,YY高級軟體工程師、四年作業經驗,拒絕咸魚爭當龍頭的斜杠程式員,
聽我說,進步多,程式人生一把梭
如果有幸能幫到你,請幫我點個【贊】,給個關注,如果能順帶評論給個鼓勵,將不勝感激,
職場亮哥文章串列:更多文章

本人所有文章、回答都與著作權保護平臺有合作,著作權歸職場亮哥所有,未經授權,轉載必究!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/235233.html
標籤:Python
