這裡介紹在 Python 使用 Protocol Buffers 格式來儲存資料的方式,以及實際應用範例。
Protocol Buffers 是一種高效率、高彈性的結構化資料序列儲存格式,其類似 XML,但更省空間、處理效率更好,而且語法也更簡單。在使用 Protocol Buffers 前,我們會先定義資料的結構規則,再使用 Protocol Buffers 的編譯器產生適用於自己專案的 API 程式碼(例如 C++ 或 Python 程式碼等),然後在程式中呼叫這個 API 來存取 Protocol Buffers 格式的資料。
首先建立一個 .proto
檔,定義好自己的 Protocol 資料格式:
/* 這是一個簡單的 Protocol 資料格式示範, 若要在 .proto 檔案中加入註解, 可使用 C/C++ 的註解語法。 */ // Protocol 資料格式版本 syntax = "proto2"; // 套件名稱 package tutorial; // 定義資料格式 message Person { // 基本資料 required string name = 1; required int32 id = 2; optional string email = 3; // 列舉型別 enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } // 定義另外一個 message message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } // 多筆 PhoneNumber 資料 repeated PhoneNumber phones = 4; } message AddressBook { // 多筆 Person 資料 repeated Person people = 1; }
.proto
檔的語法相當類似 C++ 或 Java 的類別定義。一開始以 package
所定義的套件名稱是為了避免不同專案之間產生名稱衝突,但在 Python 之中都是以目錄結構來區別套件,所以 package
在 Python 中並沒有太多作用(但是在其他程式語言中就會需要,所以還是建議要加這一行)。
在套件名稱定義之後,接著就是主要的資料格式,這裡的 message
就是打包許多資料的容器,一個 message
裡面可以包含各種的資料,例如 布林值(bool
)、整數(int32
)、單精度浮點數(float
)、雙精度浮點數(double
)與字串(string
),而一個 message
之中也可以包含其他的 message
。
在這個例子中,我們定義一個名為 Person
的 message
,其除了包含一些基本資料外,還包含多個名為 PhoneNumber
的 message
,而最後又定義了一個 AddressBook
的 message
,其中包含多筆 Person
的 message
。
我們也可以在一個 message
當中又定義另一個 message
(就像這裡的PhoneNumber
定義放在 Persion
之中)。
enum
是一種列舉型別,可限制欄位的資料只能是某些預定的值,例如這裡的 PhoneType
的值就只能是 MOBILE
、HOME
或 WORK
。
在定義欄位時,欄位後方所加上的 = 1
與 = 2
等編號是代表欄位的識別標籤(tag),這些標籤在二進位編碼時會被使用到,標籤 1
到 15
在編碼時會比較省空間,16
以上的標籤在儲存時會多出一個位元組(byte),通常我們會讓重複出現(repeated)的欄位使用 15
以下的標籤,而不常出現的欄位則使用 16
以上的標籤,達到最佳化的的目的。
每一個欄位都必須用以下其中一種 modifier 來標示該欄位的出現次數:
required
:必要欄位,不可省略,否則資料解析時就會出錯。optional
:選擇性欄位,若省略則會以預設值作為其值。repeated
:重複性欄位,表示該欄位可重複出現任何次數,或是完全不出現。在 Protocol Buffers 資料中會保留重複出現資料的順序,就類似陣列的概念。建立好 .proto
檔案之後,接著就可以用 Protocol Buffers 的編譯器將 .proto
檔案編譯出可用來存取 AddressBook
(以及 Persion
與 PhoneNumber
)資料的 API 程式碼。
首先從 GitHub 上下載 Protocol Buffers 的編譯器,並安裝起來。若在 Ubuntu Linux 上,可直接使用 apt 安裝:
sudo apt-get install protobuf-compiler
安裝完後,編譯 .proto
檔案:
# 程式碼所在目錄 SRC_DIR=`pwd` # 輸出目錄 DST_DIR=`pwd` # 編譯 .proto 檔 protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto
這裡我們要編譯出 Python 的 API,所以使用 --python_out
這個參數指定輸出目錄,若要輸出其他程式語言的 API,可以自行修改(例如 --cpp_out
與 --java_out
)。
編譯之後,就會產生一個 addressbook_pb2.py
Python 指令稿,其中就包含了存取 AddressBook
用的 API。
產生了存取 AddressBook
用的 API 之後,就可以在自己的 Python 程式中使用,以下是寫入 Protocol Buffers 資料的範例。
import addressbook_pb2 import sys # 指定 Protocol Buffer 資料檔 my_pb_file = "my_addr_book.pb" # 建立 AddressBook address_book = addressbook_pb2.AddressBook() # 增加一筆 Person 資料 person = address_book.people.add() # 設定 Person 基本資料 person.id = 123 person.name = "G. T. Wang" person.email = "guozhao.wang@gmail.com" # 新增第一筆電話 phone_number = person.phones.add() phone_number.number = "0912-345678" phone_number.type = addressbook_pb2.Person.MOBILE # 新增第二筆電話 phone_number = person.phones.add() phone_number.number = "06-1234567" phone_number.type = addressbook_pb2.Person.WORK # 寫入 AddressBook with open(my_pb_file, "wb") as f: f.write(address_book.SerializeToString())
此程式執行後,就會產生一個 my_addr_book.pb
二進位檔案,其內容就是這裡建立的 AddressBook
。
以下是讀取 Protocol Buffers 資料的範例。
import addressbook_pb2 import sys # 指定 Protocol Buffer 資料檔 my_pb_file = "my_addr_book.pb" # 建立 AddressBook address_book = addressbook_pb2.AddressBook() # 寫入 AddressBook with open(my_pb_file, "rb") as f: address_book.ParseFromString(f.read()) # 顯示資料 for person in address_book.people: print("Person ID:", person.id) print(" Name:", person.name) if person.HasField('email'): print(" E-mail address:", person.email) for phone_number in person.phones: if phone_number.type == addressbook_pb2.Person.MOBILE: print(" Mobile phone #:", phone_number.number) elif phone_number.type == addressbook_pb2.Person.HOME: print(" Home phone #:", phone_number.number) elif phone_number.type == addressbook_pb2.Person.WORK: print(" Work phone #:", phone_number.number)
此程式執行後,就可以列出上面建立好的 AddressBook
內容:
Person ID: 123 Name: G. T. Wang E-mail address: guozhao.wang@gmail.com Mobile phone #: 0912-345678 Work phone #: 06-1234567
參考資料:Google Developers