PythonでProtocol Buffersの文字列と数値の単純なメッセージを操作する

gRPCのシリアライズのフォーマットとして使われるProtocol Buffersをさわってみる。
protobufをインストールし、.protoファイルからPythonのコードを生成する。そのコードを使ってシリアライズ、デシリアライズを試す。

Protocol Buffersとは

Protocol Buffersは、構造化されたデータをシリアライズする仕組みだ。
構造化されたデータとは、値とその値がどんな意味をもつのか整理されたデータだ。
シリアライズとは、プログラムで使われているメモリ内のオブジェクトを、ディスクに保存したりネットワーク経由で送信したりするためにバイトストリーム(一連のバイト列)に変換することだ。

Protocol Buffersを用いた開発

Protocol Buffersを用いた開発は次の手順で行なう。

  1. .protoファイルを作成する
  2. .protoファイルを指定した言語のコードへ変換(コンパイル)する
  3. 変換されたコードを使い、オブジェクトをシリアライズおよびデシリアライズ(バイトストリームからオブジェクトへの変換)する

実際にファイルを作成しながら、1つずつ見ていく。

.protoファイルを作成する

.protoファイルはコードを生成する元になるファイルだ。
ファイルは独自の構文にしたがって記述していく。
最初の行のsyntaxにはファイル内で使う構文のバージョンを記載する。指定しない場合proto2が使われる。2019/6/1時点ではproto3が最新であるため、新しい構文を使いたい場合は明示的に指定する必要がある。
コード中でオブジェクトと呼ばれているものは、.protoファイルではメッセージと呼ばれている。
メッセージを定義するにはmessage メッセージ名 {}のようにmessageという文言に続いてメッセージ名を定義する。 メッセージはフィールドごとに型、フィールド名、一意な番号をつける。 型は文字列を示すstringや整数を示すint32だけでなく、浮動小数点を示すfloatや真偽値を示すbool用意されている

person.proto

syntax = "proto3";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

.protoファイルを指定した言語のコードへ変換(コンパイル)する

作成した.protoファイルをPythonのコードに変換するため、protobufをインストールする。
Macではbrewを使いprotobufをインストールする。

$ brew install protobuf
$ protoc --version
libprotoc 3.7.1

インストールしたprotobufを使い、.protoファイルからPythonのコードを出力する。
protoc --python_out=[Pythonのコードを出力するディレクトリ] [.protoファイルのパス]

$ protoc --python_out=./ ./person.proto

Pythonのコードは、.protoファイルのファイル名に_pb2の接尾語がついたファイル名で出力される。

.
├── person.proto
└── person_pb2.py

変換されたコードを使い、オブジェクトをシリアライズおよびデシリアライズする

出力されたperson_pb2.pyをインポートして、オブジェクトのシリアライズおよびデシリアライズを試していく。

オブジェクトをシリアライズする

person_pb2モジュールからPersonクラスをインポートし、オブジェクトからバイナリー文字列を出力するSerializeToString()メソッドや、バイナリー文字列からオブジェクトを生成するParseFromString(data)メソッドを持つオブジェクトを生成する。

main.py

from person_pb2 import Person

person = Person()
person.id = 1234
person.name = "Jhon"
person.email = "jhon@example.com"
print(person.SerializeToString())

main.pyをそのまま実行すると以下のエラーが表示される。
person_pb2モジュールで読み込んでいるPersonクラスでgoogle.protobufを読み込んでいるが、依存関係を解消していないためエラーになる。

from google.protobuf import descriptor as _descriptor
ModuleNotFoundError: No module named 'google'

依存関係を解消するためにprotobufpipenv(pip)でインストールする。

$ pipenv install protobuf

protobufをインストールした上で実行すると、バイナリー文字列が出力される。

$ pipenv run python main.py
b'\n\x04Jhon\x10\xd2\t\x1a\x10jhon@example.com'

バイナリーをデシリアライズする

ParseFromStringでシリアライズ結果のバイナリー文字列を読み込む。

from person_pb2 import Person

person = Person()
print('before', person)
person.ParseFromString(b'\n\x04Jhon\x10\xd2\t\x1a\x10jhon@example.com')
print('after', person)

実行するとバイト文字列からオブジェクトが生成されていることがわかる。

$ python read.py
before
after name: "Jhon"
id: 1234
email: "jhon@example.com"

・参考
https://developers.google.com/protocol-buffers/
https://developers.google.com/protocol-buffers/docs/pythontutorial
https://stackoverflow.com/questions/633402/what-is-serialization