プロトコルバッファの基本(Pythonバージョン)
翻訳元:https : //developers.google.com/protocol-buffers/docs/pythontutorial
プロトコルバッファを使用する必要性は、主に次の3つのステップに分かれています。
- メッセージ形式で.protoファイルを定義する
- プロトコルバッファコンパイラを使用して.pyファイルを生成する(他の言語と同様)
- PythonプロトコルバッファAPIを使用してメッセージを読み書きする
この記事は、詳細情報の基本的な紹介にすぎません。
- 詳細なドキュメント:https : //developers.google.com/protocol-buffers/docs/overview
- バージョンのダウンロード:https : //developers.google.com/protocol-buffers/docs/downloads
プロトコルバッファの利点
- Pythonの組み込み酸洗い:スキーマの進化に適切に対応できず、C ++またはJavaプログラムとデータをうまく共有できません。
- 文字列に変換する方法をカスタマイズできます。コードのコーディングと解析が必要ですが、解析には一定の時間コストがかかります。ただし、その単純さと柔軟性のため、非常に単純なデータの処理に特に適しています。
- XML:XMLは、その可読性と複数の言語との互換性のために使用されます。ただし、多くのストレージ容量を必要とし、エンコードとデコードにも多くの時間コストがかかります最後に、XML DOMツリーのトラバースは、特定のクラスのトラバースよりもはるかに複雑です。
プロトコル形式を定義する
これは、説明のためのアドレス帳の例です。アドレス帳には複数の連絡先が含まれ、各連絡先には名前、ID、電子メールアドレス、連絡先番号が含まれます。このようなアドレス帳の.protoファイルは次のように定義されています。この例はproto2に基づいており、最新バージョンはproto3です。
// addressbook.proto
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 PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
.protoファイルは最初にパッケージ名を指定します。これは、さまざまなプロジェクトで起こり得る名前の競合を解決するのに役立ちます。しかし、Pythonはファイルパスを通じてパッケージ名を管理するためです。したがって、コンテンツのこの部分は、Python以外の言語でのみ機能します。
次にメッセージの定義です。messageは、bool、int32、float、double、stringなど、多くの標準的な単純なデータ型を提供します。もちろん、複雑なデータ構造を定義することもできます。たとえば、上記のプログラムでは、PersonにはPhoneNumberが含まれ、同時にAddressBookにはPersonが含まれています。事前定義された変数の値を列挙することもできます。
変数の後の「= 1」、「= 2」は、各変数に一意のラベルを与えます。同時に、1〜15で使用されるコードサイズは16以上のバイトよりも1バイト少ないため、通常、1〜15は必須フィールドと繰り返しフィールド用に予約されています。同時に、繰り返しフィールドの各要素を再エンコードする必要があるため、この最適化は繰り返しフィールドに非常に適しています。
同時に、上記の例では、各フィールドに次のような修飾子が必要であることがわかります。
- 必須:このフィールドは割り当てられている必要があります。割り当てられていない場合、そのようなメッセージがシリアル化されると例外が返され、初期化されていないメッセージの解析に失敗します。さらに、必須とオプションの間に実質的な違いはありません。
- オプション:このフィールドの割り当てはオプションです。割り当てられていない場合は、デフォルトの割り当てが使用されます。単純なクラスの場合は、上の例のタイプなど、自分で指定できます。同時に、各シンプルクラスは、数値タイプ(0)、文字列(空の文字列)、論理変数(false)などのシステムデフォルトの初期値を提供します。埋め込みメッセージの場合、デフォルト値は常にメッセージのデフォルトのインスタンスまたはプロトタイプであり、そのドメインは設定されていません。明示的に割り当てられていないフィールドにアクセスすると、常にデフォルト値が取得されます。
- 繰り返し:これは何度でも繰り返すことができます(0を含む)。繰り返される値のシーケンスは、プロトコルバッファーに格納されます。繰り返しは、動的配列として理解できます。
ここで、requiredは永続的であることに注意してください。したがって、ドメインを必要に応じて定義する場合は、十分に注意する必要があります。後で変更する必要がある場合、古いプログラムのユーザーは、メッセージが正しく割り当てられていないと判断し、エラーを報告します。同時に、Googleは内部で必要なものを保持する必要があるかどうかについても議論しました。プロトコルバッファ3はこれらの修飾子を明示的に指定しなくなったようです。
プロトコルバッファのコンパイル
次のWebサイトhttps://developers.google.com/protocol-buffers/docs/downloadsから コンパイラを インストールし、指示に従ってください。
protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto
パラメータの説明:
- $ SRC_DIR:アプリケーションのコード。指定しない場合、現在のパスが使用されます
- $ DST_DIR:生成されたコードを配置する場所。この例はPythonに基づいているため、他の言語と同様に--python_outを使用します。
- 最後は.protoファイルへのパスです
このコマンドは、addressbook_pb2.pyファイルを生成します。
プロトコルバッファAPI
JavaおよびC ++の場合、プロトコルバッファーコードが生成されると、データにアクセスするためのコードが直接提供されますが、Pythonの場合は提供されません。addressbook_pb2.pyには以下が含まれます。
class Person(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
class PhoneNumber(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
DESCRIPTOR = _PERSON_PHONENUMBER
DESCRIPTOR = _PERSON
class AddressBook(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
DESCRIPTOR = _ADDRESSBOOK
各クラスの重要な情報は__metaclass__ = Reflection.GeneratedProtocolMessageTypeです。特定のPythonメタクラスがどのように機能するかは、この記事の範囲を超えている可能性がありますが、クラスを作成するときに使用する必要があるテンプレートとして理解できます。ロード時に、GeneratedProtocolMessageTypeメタクラスは特定の記述子を使用して、必要なすべてのPythonメソッドを生成し、対応するクラスに追加します。その後、完全に入力されたクラスをコードで使用できます。
たとえば、この記事の前の例では、次のようにメッセージで定義されたPersonクラスを使用できます。
import addressbook_pb2
person = addressbook_pb2.Person()
person.id = 1234
person.name = "John Doe"
person.email = "[email protected]"
phone = person.phones.add()
phone.number = "555-4321"
phone.type = addressbook_pb2.Person.HOME
上記の割り当てプロセスは、クラスのメンバーの単純な追加ではありません。存在しない変数を割り当てるとAttributeErrorが返され、誤った型を割り当てるとTypeErrorが返されます。割り当てる前に変数にアクセスすると、そのデフォルト値が返されます。
person.no_such_field = 1 # raises AttributeError
person.id = "1234" # raises TypeError
列挙クラス
列挙型クラスは、メタクラスによって整数値を持つ一連のシンボリック定数に拡張されます。たとえば、上記の例では、addressbook_pb2.Person.WORKの値は2です。
標準メッセージ方式
各メッセージには、メッセージ全体を確認または操作できる特定の機能も含まれています。
- IsInitialized():必要なものがすべて割り当てられているかどうかを確認します
- __str __():読み取り可能なメッセージ表現を返します。通常、str(message)およびprint(message)のデバッグに使用されます
解析とシリアル化
- SerializeToString():メッセージをシリアル化して文字列を返します。返されるバイトはテキストではなくバイナリであり、strタイプを使用してのみ保存できます
- ParseFromString(data):指定された文字列からメッセージを解析します
その他の解析およびシリアル化関数は、次を参照できます:https : //developers.google.com/protocol-buffers/docs/reference/python/google.protobuf.message.Message-class
これはOOに基づいて設計されていることに注意してください。プロトコルバッファーは、基本的にはデータストレージのダムです。これは、C言語の構造に多少似ており、オブジェクトモデルとの互換性が低いことを示しています。拡張クラスに豊富な動作を追加したい場合、最適な方法は、拡張プロトコルバッファクラスをアプリケーションクラスにカプセル化することです。別のプロジェクトでプロトコルバッファーを再利用し、.protoファイルの設計を制御できない場合は、カプセル化も良い方法です。カプセル化により、特定のアプリケーションの特定の環境に、より適切に適応できます。たとえば、一部のデータとメソッドを非表示にしたり、いくつかの便利な機能を開いたりできます。ただし、継承によってクラスを拡張することはできません。これは内部構造を破壊し、オブジェクト指向の良いアプローチではありません。
メッセージの読み書き
#! /usr/bin/python
import addressbook_pb2
import sys
# This function fills in a Person message based on user input.
def PromptForAddress(person):
person.id = int(raw_input("Enter person ID number: "))
person.name = raw_input("Enter name: ")
email = raw_input("Enter email address (blank for none): ")
if email != "":
person.email = email
while True:
number = raw_input("Enter a phone number (or leave blank to finish): ")
if number == "":
break
phone_number = person.phones.add()
phone_number.number = number
type = raw_input("Is this a mobile, home, or work phone? ")
if type == "mobile":
phone_number.type = addressbook_pb2.Person.MOBILE
elif type == "home":
phone_number.type = addressbook_pb2.Person.HOME
elif type == "work":
phone_number.type = addressbook_pb2.Person.WORK
else:
print "Unknown phone type; leaving as default value."
# Main procedure: Reads the entire address book from a file,
# adds one person based on user input, then writes it back out to the same
# file.
if len(sys.argv) != 2:
print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"
sys.exit(-1)
address_book = addressbook_pb2.AddressBook()
# Read the existing address book.
try:
f = open(sys.argv[1], "rb")
address_book.ParseFromString(f.read())
f.close()
except IOError:
print sys.argv[1] + ": Could not open file. Creating a new one."
# Add an address.
PromptForAddress(address_book.people.add())
# Write the new address book back to disk.
f = open(sys.argv[1], "wb")
f.write(address_book.SerializeToString())
f.close()
#! /usr/bin/python
import addressbook_pb2
import sys
# Iterates though all people in the AddressBook and prints info about them.
def ListPeople(address_book):
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 #: ",
elif phone_number.type == addressbook_pb2.Person.HOME:
print " Home phone #: ",
elif phone_number.type == addressbook_pb2.Person.WORK:
print " Work phone #: ",
print phone_number.number
# Main procedure: Reads the entire address book from a file and prints all
# the information inside.
if len(sys.argv) != 2:
print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"
sys.exit(-1)
address_book = addressbook_pb2.AddressBook()
# Read the existing address book.
f = open(sys.argv[1], "rb")
address_book.ParseFromString(f.read())
f.close()
ListPeople(address_book)
既存のプロトコルバッファーを拡張する
プロトコルバッファコードを解放した後、必然的にコードをアップグレードする必要がある状況があります。現時点で、アップグレードされたコードが以前のコードで引き続き正常に実行される必要がある場合(下位互換性)、アップグレードされたコードは次の要件を満たします。
- 既存のドメインのラベル番号は変更しないでください(1、2、3、...)
- 必須フィールドを追加または削除しないでください
- オプションまたは繰り返しフィールドを削除できます
- 新しいオプションフィールドまたは繰り返しフィールドを追加できますが、新しいラベル番号を使用する必要があります。削除されたドメインで使用されているラベル番号が機能しない場合でも、新しいラベル番号が使用されていてはなりません
- 他にもいくつかルールがありますが、遭遇することはありません。必要な場合は、https://developers.google.com/protocol-buffers/docs/proto#updatingを参照して ください。
そのため、古いコードは新しいメッセージを正常に読み取り、新しいフィールドを無視することができます。同時に、コードは削除されたオプションフィールドのデフォルト値を直接使用し、繰り返しフィールドは空白になります。新しいコードはまた、古いメッセージを直接読み取ります。ただし、古いコードでは新しいオプションフィールドは提供されないことに注意してください。フラグ番号の後に[default = value]を使用して、フラグhas_に明示的にアクセスするか、デフォルト値を指定する必要があるかどうかを確認する必要があります。デフォルト値が指定されていない場合は、システムのデフォルト値が使用されます。同時に、繰り返しフィールドについては、has_フラグがないため、新しいコードが割り当てられていないため、または古いコードがまったく設定されていないために、それが空であるかどうかを知ることはできません。
高度な使用
次のWebサイトhttps://developers.google.com/protocol-buffers/docs/reference/python/ で、より詳細な手順を入手でき ます。
重要な特性の1つは反射です。特定のメッセージタイプのコードを変更せずに、メッセージのフィールドを反復処理して値を変更できます。非常に便利な機能は、リフレクションを使用して、プロトコルバッファーと他のエンコード形式(XMLやJSONなど)との間の相互変換を実現することです。また、高レベルの機能は、リフレクションを使用して、同じメッセージタイプの違いを検出したり、プロトコルメッセージの従来の一連の表現を開発したりできることです。これにより、特徴的なメッセージの内容に一致する式を記述できます。同時に、想像力を駆使して、プロトコルバッファーを使用して、発生する可能性のあるより多くの問題を解決できます。
リフレクションの詳細については、https://developers.google.com/protocol-buffers/docs/reference/python/google.protobuf.message.Message-classを参照してください。