今回は、この辺りを参考にしながらプロトコルバッファ(Protobuf)を試してみる。
Protobufを調べていると、Message Packも目にすることが多い。
MessagePack、Protobufも両方ともシリアライズ・デシリアライズの機能があるので比較されるのだろう。
MessagePackがシリアライズ・デシリアライズの速度に重きを置いている。
一方、Protobufではシリアライズ・デシリアライズの速度はMessagePackと比べると遅いようだが、データ構造を記述して共有することができる。つまり、XMLやJSONと同じようにスキーマを記述することができる。
今回はProtobufで、どのようにスキーマを記述するのか、シリアライズ・デシリアライズをどうやるのかについて調べる。
Protobuf を Windows にインストール
何はともあれインストールする。
>protoc --version libprotoc 3.21.9 >pip install protobuf >pip list Package Version ------------------------ --------- protobuf 4.21.6
.proto
ファイルを作成し、コンパイルしてみる
user.proto
ファイルを作成する。中身はこんな感じ。
syntax = "proto3"; package hands_on; message GetUserRequest { int32 id = 1; } message User { int32 id = 1; string name = 2; } service UserApi { rpc GetUser (GetUserRequest) returns (User); }
細かい説明は後述するので、ここでは概略のみ。
syntax = "proto3";
は、Protocol Buffersのシンタックスのバージョンを指している。
次に、package hands_on;
だが、これはhands_on
という名前空間(package
)を定義している。
message GetUserRequest {...}
では、GetUserRequest
という名前のメッセージフォーマット(データ構造、スキーマ)を定義している。
次に、.proto
ファイルをコンパイルする。Pythonで利用できるuser_pb2.py
が作成される。
>protoc --python_out=./test user.proto > dir test 2022/11/20 01:16 <DIR> . 2022/11/20 01:16 <DIR> .. 2022/11/20 01:16 1,239 user_pb2.py
user_pb2.py
の中身はコチラ。
# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: user.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nuser.proto\x12\x08hands_on\"\x1c\n\x0eGetUserRequest\x12\n\n\x02id\x18\x01 \x01(\x05\" \n\x04User\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t2>\n\x07UserApi\x12\x33\n\x07GetUser\x12\x18.hands_on.GetUserRequest\x1a\x0e.hands_on.Userb\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'user_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _GETUSERREQUEST._serialized_start=24 _GETUSERREQUEST._serialized_end=52 _USER._serialized_start=54 _USER._serialized_end=86 _USERAPI._serialized_start=88 _USERAPI._serialized_end=150 # @@protoc_insertion_point(module_scope)
main.py
を作成し、user_pb2.py
をインポートしてスキーマを試してみる。
main.py
import user_pb2 user_rq = user_pb2.GetUserRequest() user = user_pb2.User() print("Request ID is", user_rq.id) print("User ID is", user.id) print("Username is", user.name ,"\n") user_rq.id = 1 user.id = 1 user.name = "Mazda" print("Request ID is", user_rq.id) print("User ID is", user.id) print("Username is", user.name ,"\n") user_rq.id = 1 user.id = "Honda" user.name = "Mazda" print("Request ID is", user_rq.id) print("User ID is", user.id) print("Username is", user.name ,"\n")
これを実行した結果がコチラ。
> python .\main.py Request ID is 0 User ID is 0 Username is Request ID is 1 User ID is 1 Username is Mazda Traceback (most recent call last): File "C:\Users\xxx\Documents\Protobuf\test\main.py", line 19, in <module> user.id = "Honda" TypeError: 'str' object cannot be interpreted as an integer
↑から、int型の初期値は0
、string型の初期値は空文字になっていることがわかる。
スキーマでint型と定義した変数にstring型を入れようとするとTypeError
となる。
このようにスキーマ定義に沿わないとちゃんとエラーにしてくれる。
次にmain2.py
を作成して、シリアライズ、デシリアライズを試してみる。
main2.py
import user_pb2 user = user_pb2.User() user.id = 1 user.name = "Mazda" serialized = user.SerializeToString() print(serialized) deserialized_user = user_pb2.User() deserialized_user.ParseFromString(serialized) print(deserialized_user)
実行結果は下記の通り。シリアライズして、デシリアライズできている。
> python main2.py b'\x08\x01\x12\x05Mazda' id: 1 name: "Mazda"
スキーマの説明
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
syntax = "proto3";
は、Protocol Buffersのシンタックスのバージョンを指している。
Protocol Buffersのシンタックスバージョンは、現在proto2
とproto3
がある。
デフォルトはproto2
なので、proto3
を使用する場合はこのように指定する必要がある。
メッセージSearchRequest
のなかに、3つのフィールドが定義されている。フィールドにはデータ型と名前が定義される。
フィールドは、メッセージ内でユニークな番号(フィールド番号)を持っている。フィールド番号はバイナリフォーマットにしたときにフィールドを特定するのに利用される。
詳細については以下が参考になりそう。
本格的に使うことになったら、以下のGoogleのドキュメント(詳細な言語仕様)を読む。
簡単だけど以上。