Python 生成コード ガイド
proto2、proto3、および Editions の生成コードの間の相違点が強調表示されます。これらの相違点は、このドキュメントで説明されている生成コードにあり、すべてのバージョンで同じであるベースメッセージクラス/インターフェースにはありません。このドキュメントを読む前に、proto2 言語ガイド、proto3 言語ガイド、および/またはEditions ガイドを読むことをお勧めします。
Python Protocol Buffers の実装は、C++ や Java とは少し異なります。Pythonでは、コンパイラは生成されたクラスの記述子を構築するためのコードのみを出力し、実際の作業はPython メタクラスが行います。このドキュメントでは、メタクラスが適用された後に得られるものについて説明します。
コンパイラの呼び出し
Protocol Buffer コンパイラは、--python_out=
コマンドラインフラグを指定して呼び出されると、Python 出力を生成します。--python_out=
オプションのパラメータは、コンパイラが Python 出力を書き込むディレクトリです。コンパイラは、入力された各 .proto
ファイルに対して .py
ファイルを作成します。出力ファイルの名前は、.proto
ファイルの名前を取り、2つの変更を加えることによって計算されます。
- 拡張子 (
.proto
) は_pb2.py
に置き換えられます。 - プロトパス (
--proto_path=
または-I
コマンドラインフラグで指定) は、出力パス (--python_out=
フラグで指定) に置き換えられます。
例えば、次のようにコンパイラを呼び出すとします。
protoc --proto_path=src --python_out=build/gen src/foo.proto src/bar/baz.proto
コンパイラは、src/foo.proto
および src/bar/baz.proto
のファイルを読み取り、2つの出力ファイル build/gen/foo_pb2.py
および build/gen/bar/baz_pb2.py
を生成します。コンパイラは必要に応じてディレクトリ build/gen/bar
を自動的に作成しますが、build
または build/gen
は作成しません。これらは既に存在している必要があります。
Protoc は、--pyi_out
パラメータを使用して Python スタブ (.pyi
) を生成できます。
.proto
ファイルまたはそのパスに Python モジュール名に使用できない文字 (ハイフンなど) が含まれている場合、それらはアンダースコアに置き換えられることに注意してください。したがって、ファイル foo-bar.proto
は Python ファイル foo_bar_pb2.py
になります。
ヒント
Python コードを出力する場合、Protocol Buffer コンパイラが直接 ZIP アーカイブに出力する機能は特に便利です。Python インタープリタは、PYTHONPATH
に置かれている場合、これらのアーカイブから直接読み取ることができます。ZIP ファイルに出力するには、単に .zip
で終わる出力場所を指定します。注意
拡張子_pb2.py
の数字 2 は、Protocol Buffers のバージョン 2 を示します。バージョン 1 は主に Google 内部で使用されていましたが、Protocol Buffers の前にリリースされた他の Python コードの一部に含まれているのを見つけることができるかもしれません。Python Protocol Buffers のバージョン 2 は完全に異なるインターフェースを持っており、Python には間違いを検出するためのコンパイル時型チェックがないため、バージョン番号を生成された Python ファイル名の目立つ部分にすることを選択しました。現在、proto2、proto3、および Editions はすべて、生成されたファイルに _pb2.py
を使用しています。パッケージ
Protocol Buffer コンパイラによって生成される Python コードは、.proto
ファイルで定義されたパッケージ名によって全く影響を受けません。代わりに、Python パッケージはディレクトリ構造によって識別されます。
メッセージ
単純なメッセージ宣言を考えます。
message Foo {}
Protocol Buffer コンパイラは、google.protobuf.Message
をサブクラス化する Foo
という名前のクラスを生成します。このクラスは具象クラスであり、抽象メソッドは未実装のまま残りません。C++ や Java とは異なり、Python の生成コードは .proto
ファイルの optimize_for
オプションの影響を受けません。実際、すべての Python コードはコードサイズのために最適化されています。
メッセージの名前が Python のキーワードである場合、そのクラスは「Python キーワードと競合する名前」セクションで説明されているように、getattr()
を介してのみアクセスできます。
独自の Foo
サブクラスを作成しないでください。生成されたクラスはサブクラス化のために設計されておらず、「脆い基底クラス」の問題を引き起こす可能性があります。さらに、実装継承は悪い設計です。
Python メッセージクラスは、Message
インターフェースによって定義されたものと、ネストされたフィールド、メッセージ、および列挙型 (以下で説明) のために生成されたもの以外に、特定のパブリックメンバーを持ちません。Message
は、バイナリ文字列からの解析とバイナリ文字列へのシリアライズを含め、メッセージ全体をチェック、操作、読み取り、または書き込みするために使用できるメソッドを提供します。これらのメソッドに加えて、Foo
クラスは以下の静的メソッドを定義します。
FromString(s)
: 指定された文字列からデシリアライズされた新しいメッセージインスタンスを返します。
テキスト形式でプロトコルメッセージを操作するために、text_format
モジュールも使用できることに注意してください。たとえば、Merge()
メソッドを使用すると、メッセージの ASCII 表現を既存のメッセージにマージできます。
ネストされた型
メッセージは別のメッセージ内で宣言できます。例:
message Foo {
message Bar {}
}
この場合、Bar
クラスは Foo
の静的メンバーとして宣言されるため、Foo.Bar
として参照できます。
Well-known Types
Protocol Buffers は、独自のメッセージタイプとともに .proto
ファイルで使用できるいくつかの既知の型を提供します。一部の WKT メッセージは、通常の Protocol Buffer メッセージメソッドに加えて特別なメソッドを持ちます。これらはgoogle.protobuf.Message
と WKT クラスの両方をサブクラス化するためです。
Any
Any メッセージの場合、Pack()
を呼び出して指定されたメッセージを現在の Any メッセージにパックしたり、Unpack()
を呼び出して現在の Any メッセージを指定されたメッセージにアンパックしたりできます。たとえば、
any_message.Pack(message)
any_message.Unpack(message)
Unpack()
は、渡されたメッセージオブジェクトの記述子と格納された記述子を比較し、一致しない場合は False
を返し、アンパックを試みません。それ以外の場合は True
を返します。
Is()
メソッドを呼び出して、Any メッセージが指定された Protocol Buffer タイプを表しているかどうかを確認することもできます。たとえば、
assert any_message.Is(message.DESCRIPTOR)
TypeName()
メソッドを使用して、内部メッセージの Protobuf タイプ名を取得します。
Timestamp
Timestamp メッセージは、ToJsonString()
/FromJsonString()
メソッドを使用して、RFC 3339 日付文字列形式 (JSON 文字列) に変換/変換できます。たとえば、
timestamp_message.FromJsonString("1970-01-01T00:00:00Z")
assert timestamp_message.ToJsonString() == "1970-01-01T00:00:00Z"
GetCurrentTime()
を呼び出して、Timestamp メッセージを現在の時刻で埋めることもできます。
timestamp_message.GetCurrentTime()
エポックからの他の時間単位の間で変換するには、ToNanoseconds(), FromNanoseconds(), ToMicroseconds(), FromMicroseconds(), ToMilliseconds(), FromMilliseconds(), ToSeconds()
、または FromSeconds()
を呼び出すことができます。生成されたコードには、Python の datetime オブジェクトとタイムスタンプの間で変換するための ToDatetime()
および FromDatetime()
メソッドもあります。たとえば、
timestamp_message.FromMicroseconds(-1)
assert timestamp_message.ToMicroseconds() == -1
dt = datetime(2016, 1, 1)
timestamp_message.FromDatetime(dt)
self.assertEqual(dt, timestamp_message.ToDatetime())
Duration
Duration メッセージは、JSON 文字列と他の時間単位の間で変換するための Timestamp と同じメソッドを持っています。timedelta と Duration の間で変換するには、ToTimedelta()
または FromTimedelta
を呼び出すことができます。たとえば、
duration_message.FromNanoseconds(1999999999)
td = duration_message.ToTimedelta()
assert td.seconds == 1
assert td.microseconds == 999999
FieldMask
FieldMask メッセージは、ToJsonString()
/FromJsonString()
メソッドを使用して JSON 文字列に変換/変換できます。さらに、FieldMask メッセージは次のメソッドを持ちます。
IsValidForDescriptor(message_descriptor)
: FieldMask がメッセージ記述子に対して有効かどうかをチェックします。AllFieldsFromDescriptor(message_descriptor)
: メッセージ記述子のすべての直接フィールドを FieldMask に取得します。CanonicalFormFromMask(mask)
: FieldMask を正規形式に変換します。Union(mask1, mask2)
: 2つの FieldMask をこの FieldMask にマージします。Intersect(mask1, mask2)
: 2つの FieldMask をこの FieldMask に交差させます。MergeMessage(source, destination, replace_message_field=False, replace_repeated_field=False)
: FieldMask で指定されたフィールドを source から destination にマージします。
Struct
Struct メッセージを使用すると、アイテムを直接取得および設定できます。たとえば、
struct_message["key1"] = 5
struct_message["key2"] = "abc"
struct_message["key3"] = True
リスト/構造体を取得または作成するには、get_or_create_list()
/get_or_create_struct()
を呼び出すことができます。たとえば、
struct.get_or_create_struct("key4")["subkey"] = 11.0
struct.get_or_create_list("key5")
ListValue
ListValue メッセージは、次のことを可能にする Python シーケンスのように動作します。
list_value = struct_message.get_or_create_list("key")
list_value.extend([6, "seven", True, None])
list_value.append(False)
assert len(list_value) == 5
assert list_value[0] == 6
assert list_value[1] == "seven"
assert list_value[2] == True
assert list_value[3] == None
assert list_Value[4] == False
ListValue/Struct を追加するには、add_list()
/add_struct()
を呼び出します。たとえば、
list_value.add_struct()["key"] = 1
list_value.add_list().extend([1, "two", True])
フィールド
メッセージ型内の各フィールドについて、対応するクラスはフィールドと同じ名前のプロパティを持ちます。プロパティを操作する方法は、その型によって異なります。
プロパティに加えて、コンパイラは各フィールドに対して、そのフィールド番号を含む整数定数を生成します。定数名は、フィールド名が大文字に変換されたもので、その後に _FIELD_NUMBER
が続きます。たとえば、フィールド int32 foo_bar = 5;
が与えられた場合、コンパイラは定数 FOO_BAR_FIELD_NUMBER = 5
を生成します。
フィールドの名前が Python のキーワードである場合、そのプロパティは「Python キーワードと競合する名前」セクションで説明されているように、getattr()
および setattr()
を介してのみアクセスできます。
Protocol Buffers は、フィールドの存在の2つのモードを定義します: explicit
と implicit
です。これらのそれぞれについて、以下のセクションで説明します。
明示的なプレゼンスを持つ単数フィールド
explicit
存在を持つ単一フィールドは、常にフィールドが設定されていない状態とデフォルト値に設定されている状態を区別できます。
メッセージ以外の型の単一フィールド foo
がある場合、通常のフィールドのように foo
フィールドを操作できます。たとえば、foo
の型が int32
の場合、次のように記述できます。
message.foo = 123
print(message.foo)
foo
を誤った型の値に設定すると、TypeError
が発生することに注意してください。
foo
が設定されていないときに読み取られると、その値はフィールドのデフォルト値になります。foo
が設定されているかどうかを確認したり、foo
の値をクリアしたりするには、Message
インターフェースの HasField()
または ClearField()
メソッドを呼び出す必要があります。たとえば、
assert not message.HasField("foo")
message.foo = 123
assert message.HasField("foo")
message.ClearField("foo")
assert not message.HasField("foo")
Editionsでは、フィールドはデフォルトでexplicit
プレゼンスを持ちます。以下はEditionsの.proto
ファイルにおけるexplicit
フィールドの例です。
edition = "2023";
message MyMessage {
int32 foo = 1;
}
暗黙的なプレゼンスを持つ単数フィールド
implicit
存在を持つ単一フィールドは、HasField()
メソッドを持ちません。implicit
フィールドは常に「設定済み」であり、フィールドを読み取ると常に値が返されます。値が割り当てられていない implicit
フィールドを読み取ると、その型のデフォルト値が返されます。
メッセージ以外の型の単一フィールド foo
がある場合、通常のフィールドのように foo
フィールドを操作できます。たとえば、foo
の型が int32
の場合、次のように記述できます。
message.foo = 123
print(message.foo)
foo
を誤った型の値に設定すると、TypeError
が発生することに注意してください。
foo
が設定されていないときに読み取られると、その値はフィールドのデフォルト値になります。foo
の値をクリアして、その型のデフォルト値にリセットするには、Message
インターフェースの ClearField()
メソッドを呼び出します。たとえば、
message.foo = 123
message.ClearField("foo")
単数メッセージフィールド
メッセージ型は少し異なります。埋め込みメッセージフィールドに値を割り当てることはできません。代わりに、子メッセージ内の任意のフィールドに値を割り当てることは、親でメッセージフィールドを設定することを意味します。サブメッセージは常に明示的な存在を持つため、親メッセージのHasField()
メソッドを使用して、メッセージ型フィールドの値が設定されているかどうかを確認することもできます。
例えば、以下の .proto
定義がある場合、
edition = "2023";
message Foo {
Bar bar = 1;
}
message Bar {
int32 i = 1;
}
次のことはできません。
foo = Foo()
foo.bar = Bar() # WRONG!
代わりに、bar
を設定するには、単に bar
内のフィールドに直接値を割り当てます。すると、foo
に bar
フィールドができます。
foo = Foo()
assert not foo.HasField("bar")
foo.bar.i = 1
assert foo.HasField("bar")
assert foo.bar.i == 1
foo.ClearField("bar")
assert not foo.HasField("bar")
assert foo.bar.i == 0 # Default value
同様に、Message
インターフェースの CopyFrom()
メソッドを使用して bar
を設定できます。これにより、bar
と同じ型の別のメッセージからすべての値がコピーされます。
foo.bar.CopyFrom(baz)
bar
内のフィールドを単に読み取っても、フィールドは設定されないことに注意してください。
foo = Foo()
assert not foo.HasField("bar")
print(foo.bar.i) # Print i's default value
assert not foo.HasField("bar")
フィールドを持たない、または設定したくないメッセージに「has」ビットが必要な場合は、SetInParent()
メソッドを使用できます。
foo = Foo()
assert not foo.HasField("bar")
foo.bar.SetInParent() # Set Foo.bar to a default Bar message
assert foo.HasField("bar")
繰り返しフィールド
繰り返されるフィールドには、スカラ、列挙型、メッセージの3つのタイプがあります。マップフィールドとoneofフィールドは繰り返すことはできません。
繰り返されるスカラおよびEnumフィールド
繰り返されるフィールドは、Python シーケンスのように動作するオブジェクトとして表されます。埋め込みメッセージと同様に、フィールドを直接割り当てることはできませんが、操作することはできます。たとえば、次のメッセージ定義が与えられた場合、
message Foo {
repeated int32 nums = 1;
}
以下のことができます。
foo = Foo()
foo.nums.append(15) # Appends one value
foo.nums.extend([32, 47]) # Appends an entire list
assert len(foo.nums) == 3
assert foo.nums[0] == 15
assert foo.nums[1] == 32
assert foo.nums == [15, 32, 47]
foo.nums[:] = [33, 48] # Assigns an entire list
assert foo.nums == [33, 48]
foo.nums[1] = 56 # Reassigns a value
assert foo.nums[1] == 56
for i in foo.nums: # Loops and print
print(i)
del foo.nums[:] # Clears list (works just like in a Python list)
Message
インターフェースの ClearField()
メソッドは、Python の del
を使用するのに加えて機能します。
値を取得するためにインデックスを使用する場合、負の数を使用できます。たとえば、リストの最後の要素を取得するために -1
を使用します。インデックスが範囲外になると、IndexError: list index out of range
が発生します。
繰り返されるメッセージフィールド
繰り返しメッセージフィールドは、繰り返しスカラフィールドと同様に機能します。ただし、対応する Python オブジェクトには、新しいメッセージオブジェクトを作成し、リストに追加し、呼び出し元が入力できるように返す add()
メソッドもあります。また、オブジェクトの append()
メソッドは、指定されたメッセージの**コピー**を作成し、そのコピーをリストに追加します。これは、可変データ構造が複数の所有者を持つ場合に発生する可能性のある循環参照やその他の混乱を避けるために、メッセージが常に親メッセージによって所有されるようにするために行われます。同様に、オブジェクトの extend()
メソッドはメッセージのリスト全体を追加しますが、リスト内のすべてのメッセージの**コピー**を作成します。
例えば、このメッセージ定義が与えられた場合、
edition = "2023";
message Foo {
repeated Bar bars = 1;
}
message Bar {
int32 i = 1;
int32 j = 2;
}
以下のことができます。
foo = Foo()
bar = foo.bars.add() # Adds a Bar then modify
bar.i = 15
foo.bars.add().i = 32 # Adds and modify at the same time
new_bar = Bar()
new_bar.i = 40
another_bar = Bar()
another_bar.i = 57
foo.bars.append(new_bar) # Uses append() to copy
foo.bars.extend([another_bar]) # Uses extend() to copy
assert len(foo.bars) == 4
assert foo.bars[0].i == 15
assert foo.bars[1].i == 32
assert foo.bars[2].i == 40
assert foo.bars[2] == new_bar # The appended message is equal,
assert foo.bars[2] is not new_bar # but it is a copy!
assert foo.bars[3].i == 57
assert foo.bars[3] == another_bar # The extended message is equal,
assert foo.bars[3] is not another_bar # but it is a copy!
foo.bars[1].i = 56 # Modifies a single element
assert foo.bars[1].i == 56
for bar in foo.bars: # Loops and print
print(bar.i)
del foo.bars[:] # Clears list
# add() also forwards keyword arguments to the concrete class.
# For example, you can do:
foo.bars.add(i=12, j=13)
# Initializers forward keyword arguments to a concrete class too.
# For example:
foo = Foo( # Creates Foo
bars=[ # with its field bars set to a list
Bar(i=15, j=17), # where each list member is also initialized during creation.
Bar(i=32),
Bar(i=47, j=77),
]
)
assert len(foo.bars) == 3
assert foo.bars[0].i == 15
assert foo.bars[0].j == 17
assert foo.bars[1].i == 32
assert foo.bars[2].i == 47
assert foo.bars[2].j == 77
繰り返しスカラーフィールドとは異なり、繰り返しメッセージフィールドはアイテム割り当て(つまり、__setitem__
)を**サポートしません**。例えば、
foo = Foo()
foo.bars.add(i=3)
# WRONG!
foo.bars[0] = Bar(i=15) # Raises an exception
# WRONG!
foo.bars[:] = [Bar(i=15), Bar(i=17)] # Also raises an exception
# WRONG!
# AttributeError: Cannot delete field attribute
del foo.bars
# RIGHT
del foo.bars[:]
foo.bars.extend([Bar(i=15), Bar(i=17)])
グループ (proto2)
グループは非推奨であり、新しいメッセージタイプを作成する際には使用すべきではないことに注意してください。代わりに、ネストされたメッセージタイプ(proto2、proto3)または区切りフィールド(エディション)を使用してください。
グループは、ネストされたメッセージタイプとフィールドを単一の宣言に結合し、メッセージに異なるワイヤ形式を使用します。生成されたメッセージはグループと同じ名前を持ちます。生成されたフィールドの名前は、グループ名の**小文字**です。
たとえば、ワイヤ形式を除けば、以下の2つのメッセージ定義は同等です。
// Version 1: Using groups
message SearchResponse {
repeated group SearchResult = 1 {
optional string url = 1;
}
}
// Version 2: Not using groups
message SearchResponse {
message SearchResult {
optional string url = 1;
}
repeated SearchResult searchresult = 1;
}
グループは required
、optional
、または repeated
のいずれかです。required または optional なグループは、通常の単一メッセージフィールドと同じ API を使用して操作されます。repeated なグループは、通常の繰り返しメッセージフィールドと同じ API を使用して操作されます。
例えば、上記の SearchResponse
定義が与えられた場合、以下のことができます。
resp = SearchResponse()
resp.searchresult.add(url="https://blog.google")
assert resp.searchresult[0].url == "https://blog.google"
assert resp.searchresult[0] == SearchResponse.SearchResult(url="https://blog.google")
マップフィールド
このメッセージ定義が与えられた場合、
message MyMessage {
map<int32, int32> mapfield = 1;
}
マップフィールド用に生成される Python API は、Python の dict
と全く同じです。
# Assign value to map
m.mapfield[5] = 10
# Read value from map
m.mapfield[5]
# Iterate over map keys
for key in m.mapfield:
print(key)
print(m.mapfield[key])
# Test whether key is in map:
if 5 in m.mapfield:
print(“Found!”)
# Delete key from map.
del m.mapfield[key]
埋め込みメッセージフィールドと同様に、メッセージをマップ値に直接割り当てることはできません。代わりに、マップ値としてメッセージを追加するには、未定義のキーを参照します。これにより、新しいサブメッセージが構築され、返されます。
m.message_map[key].submessage_field = 10
未定義のキーについては、次のセクションで詳しく説明します。
未定義のキーの参照
プロトコルバッファのマップのセマンティクスは、未定義のキーに関しては、Python の dict
とは少し異なる動作をします。通常の Python の dict
では、未定義のキーを参照すると KeyError 例外が発生します。
>>> x = {}
>>> x[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 5
しかし、Protocol Buffers のマップでは、未定義のキーを参照すると、マップ内にゼロ/false/空の値を持つキーが作成されます。この動作は、Python 標準ライブラリの defaultdict
に似ています。
>>> dict(m.mapfield)
{}
>>> m.mapfield[5]
0
>>> dict(m.mapfield)
{5: 0}
この動作は、メッセージ型を持つマップにとって特に便利です。なぜなら、返されたメッセージのフィールドを直接更新できるからです。
>>> m.message_map[5].foo = 3
メッセージフィールドに値を割り当てなくても、サブメッセージはマップ内に作成されることに注意してください。
>>> m.message_map[10]
<test_pb2.M2 object at 0x7fb022af28c0>
>>> dict(m.message_map)
{10: <test_pb2.M2 object at 0x7fb022af28c0>}
これは、通常の埋め込みメッセージフィールドとは**異なります**。埋め込みメッセージフィールドでは、メッセージ自体はフィールドの1つに値を割り当てた後にのみ作成されます。
例えば、m.message_map[10]
だけでサブメッセージが作成される可能性があることは、コードを読む人にはすぐに明らかではないかもしれないため、同じことを行うが、メッセージ作成の可能性をより明確にする get_or_create()
メソッドも提供しています。
# Equivalent to:
# m.message_map[10]
# but more explicit that the statement might be creating a new
# empty message in the map.
m.message_map.get_or_create(10)
列挙型
Pythonでは、enumは単なる整数です。enumの定義された値に対応する整数定数のセットが定義されます。例えば、
message Foo {
enum SomeEnum {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
SomeEnum bar = 1;
}
定数 VALUE_A
、VALUE_B
、および VALUE_C
は、それぞれ値 0、5、および 1234 で定義されています。必要に応じて SomeEnum
にアクセスできます。enumが外部スコープで定義されている場合、値はモジュール定数になります。メッセージ内で定義されている場合 (上記のように)、それらはそのメッセージクラスの静的メンバーになります。
例えば、プロト内の次の enum の値にアクセスするには、次の3つの方法があります。
enum SomeEnum {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
value_a = myproto_pb2.SomeEnum.VALUE_A
# or
myproto_pb2.VALUE_A
# or
myproto_pb2.SomeEnum.Value('VALUE_A')
enum フィールドは、スカラ フィールドと全く同じように動作します。
foo = Foo()
foo.bar = Foo.VALUE_A
assert foo.bar == 0
assert foo.bar == Foo.VALUE_A
enumの名前(またはenum値)がPythonのキーワードである場合、そのオブジェクト(またはenum値のプロパティ)は、「Pythonキーワードと競合する名前」セクションで説明されているように、getattr()
を介してのみアクセスできます。
proto2ではenumはクローズドですが、proto3ではenumはオープンです。Editionsでは、enum_type
機能がenumの動作を決定します。
OPEN
enum は、enum 定義で指定されていない場合でも、任意のint32
値を持つことができます。これは Editions のデフォルトです。CLOSED
列挙型は、列挙型に対して定義された値以外の数値を格納できません。列挙型にない値を割り当てると、生成されたコードは例外をスローします。これは、proto2 の列挙型の動作に相当します。
enum には、値からフィールド名を取得したり、その逆を行ったり、フィールドのリストを取得したりするためのユーティリティメソッドが多数あります。これらはenum_type_wrapper.EnumTypeWrapper
(生成された enum クラスの基本クラス)で定義されています。したがって、例えば、myproto.proto
に以下のスタンドアロン enum がある場合、
enum SomeEnum {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
...このようにできます。
self.assertEqual('VALUE_A', myproto_pb2.SomeEnum.Name(myproto_pb2.VALUE_A))
self.assertEqual(5, myproto_pb2.SomeEnum.Value('VALUE_B'))
上記のような Foo のように、プロトコルメッセージ内で宣言された enum の場合、構文は似ています。
self.assertEqual('VALUE_A', myproto_pb2.Foo.SomeEnum.Name(myproto_pb2.Foo.VALUE_A))
self.assertEqual(5, myproto_pb2.Foo.SomeEnum.Value('VALUE_B'))
複数の enum 定数が同じ値(エイリアス)を持つ場合、最初に定義された定数が返されます。
enum SomeEnum {
option allow_alias = true;
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
VALUE_B_ALIAS = 5;
}
上記の例では、myproto_pb2.SomeEnum.Name(5)
は "VALUE_B"
を返します。
Oneof
oneofを持つメッセージが与えられた場合、
message Foo {
oneof test_oneof {
string name = 1;
int32 serial_number = 2;
}
}
Foo
に対応する Python クラスには、通常のフィールドと同様に name
と serial_number
というプロパティがあります。ただし、通常のフィールドとは異なり、oneof のフィールドは同時に最大1つしか設定できません。これはランタイムによって保証されます。たとえば、
message = Foo()
message.name = "Bender"
assert message.HasField("name")
message.serial_number = 2716057
assert message.HasField("serial_number")
assert not message.HasField("name")
メッセージクラスには、oneof内のどのフィールド(もしあれば)が設定されているかを調べることができる WhichOneof
メソッドもあります。このメソッドは、設定されているフィールドの名前を返すか、何も設定されていない場合は None
を返します。
assert message.WhichOneof("test_oneof") is None
message.name = "Bender"
assert message.WhichOneof("test_oneof") == "name"
HasField
および ClearField
も、フィールド名に加えて oneof 名を受け入れます。
assert not message.HasField("test_oneof")
message.name = "Bender"
assert message.HasField("test_oneof")
message.serial_number = 2716057
assert message.HasField("test_oneof")
message.ClearField("test_oneof")
assert not message.HasField("test_oneof")
assert not message.HasField("serial_number")
oneofに対して ClearField
を呼び出すと、現在設定されているフィールドのみがクリアされることに注意してください。
Pythonキーワードと競合する名前
メッセージ、フィールド、enum、または enum 値の名前がPython キーワードである場合、その対応するクラスまたはプロパティの名前は同じですが、Python の通常の属性参照構文 (つまりドット演算子) を介してではなく、Python の組み込み関数getattr()
およびsetattr()
を使用してのみアクセスできます。
例えば、以下の .proto
定義がある場合、
message Baz {
optional int32 from = 1
repeated int32 in = 2;
}
これらのフィールドには次のようにアクセスします。
baz = Baz()
setattr(baz, "from", 99)
assert getattr(baz, "from") == 99
getattr(baz, "in").append(42)
assert getattr(baz, "in") == [42]
対照的に、これらのフィールドにアクセスするために obj.attr
構文を使用しようとすると、Python はコードの解析時に構文エラーを発生させます。
# WRONG!
baz.in # SyntaxError: invalid syntax
baz.from # SyntaxError: invalid syntax
エクステンション
拡張範囲を持つ proto2 または editions メッセージが与えられた場合、
edition = "2023";
message Foo {
extensions 100 to 199;
}
Foo
に対応する Python クラスには、Extensions
というメンバーがあります。これは、拡張識別子を現在の値にマッピングする辞書です。
エクステンション定義が与えられた場合:
extend Foo {
int32 bar = 123;
}
プロトコルバッファコンパイラは、bar
という「拡張識別子」を生成します。この識別子は Extensions
辞書のキーとして機能します。この辞書で値を検索した結果は、同じ型の通常のフィールドにアクセスした場合とまったく同じです。したがって、上記の例が与えられた場合、次のことができます。
foo = Foo()
foo.Extensions[proto_file_pb2.bar] = 2
assert foo.Extensions[proto_file_pb2.bar] == 2
文字列名だけでなく、拡張識別子定数を指定する必要があることに注意してください。これは、異なるスコープで同じ名前を持つ複数の拡張機能を指定できるためです。
通常のフィールドと同様に、Extensions[...]
は単一メッセージの場合はメッセージオブジェクトを返し、繰り返しフィールドの場合はシーケンスを返します。
Message
インターフェースの HasField()
および ClearField()
メソッドは拡張機能では機能しません。代わりに、HasExtension()
および ClearExtension()
を使用する必要があります。HasExtension()
および ClearExtension()
メソッドを使用するには、存在を確認する拡張機能の field_descriptor
を渡します。
サービス
.proto
ファイルに次の行が含まれている場合、
option py_generic_services = true;
このセクションで説明するように、プロトコルバッファコンパイラは、ファイル内で見つかったサービス定義に基づいてコードを生成します。ただし、生成されたコードは特定のRPCシステムに結び付けられていないため、1つのシステムに特化したコードよりも多くの間接的なレベルを必要とするため、望ましくない場合があります。このコードを生成したくない場合は、次の行をファイルに追加してください。
option py_generic_services = false;
上記のいずれの行も指定されていない場合、オプションはデフォルトで false
になります。これは、汎用サービスが非推奨であるためです。(2.4.0より前は、オプションのデフォルトは true
であったことに注意してください)
.proto
言語サービス定義に基づく RPC システムは、システムに適したコードを生成するためのプラグインを提供する必要があります。これらのプラグインは、同じ名前の独自のクラスを生成できるように、抽象サービスが無効になっていることを要求する可能性があります。
このセクションの残りの部分では、抽象サービスが有効になっている場合に Protocol Buffer コンパイラが何を生成するかについて説明します。
インターフェース
サービス定義が与えられた場合
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
プロトコルバッファコンパイラは、このサービスを表す Foo
というクラスを生成します。Foo
は、サービス定義で定義された各メソッドに対応するメソッドを持ちます。この場合、メソッド Bar
は次のように定義されます。
def Bar(self, rpc_controller, request, done)
パラメータは、Service.CallMethod()
のパラメータと同等ですが、method_descriptor
引数は暗黙的です。
これらの生成されたメソッドは、サブクラスによってオーバーライドされることを意図しています。デフォルトの実装は、メソッドが未実装であることを示すエラーメッセージを付けてcontroller.SetFailed()
を呼び出し、その後done
コールバックを呼び出します。独自のサービスを実装する場合は、この生成されたサービスをサブクラス化し、そのメソッドを適切に実装する必要があります。
Foo
はService
インターフェースをサブクラス化します。Protocol Bufferコンパイラは、Service
のメソッドの実装を次のように自動的に生成します。
GetDescriptor
: サービスのServiceDescriptor
を返します。CallMethod
: 提供されたメソッド記述子に基づいて、どのメソッドが呼び出されているかを判断し、それを直接呼び出します。GetRequestClass
とGetResponseClass
: 指定されたメソッドの適切な型の要求または応答のクラスを返します。
スタブ
Protocol Buffer コンパイラは、すべてのサービスインターフェースの「スタブ」実装も生成します。これは、サービスを実装するサーバーにリクエストを送信したいクライアントによって使用されます。上記の Foo
サービスの場合、スタブ実装 Foo_Stub
が定義されます。
Foo_Stub
はFoo
のサブクラスです。そのコンストラクタはRpcChannel
をパラメータとして受け取ります。スタブは、チャネルのCallMethod()
メソッドを呼び出すことによって、サービスの各メソッドを実装します。
Protocol Buffer ライブラリには RPC 実装は含まれていません。しかし、生成されたサービス クラスを任意の選択した RPC 実装に接続するために必要なすべてのツールが含まれています。RpcChannel
とRpcController
の実装を提供するだけで済みます。
プラグイン挿入ポイント
Python コードジェネレーターの出力を拡張したいコードジェネレータープラグインは、指定された挿入ポイント名を使用して、次のタイプのコードを挿入できます。
imports
: インポートステートメント。module_scope
: 最上位の宣言。
警告
標準のコードジェネレータによって宣言されたプライベートクラスメンバーに依存するコードは生成しないでください。これらの実装詳細は、Protocol Buffers の将来のバージョンで変更される可能性があります。PythonとC++間でメッセージを共有する
Protobuf Python API の 4.21.0 バージョン以前では、Python アプリはネイティブ拡張機能を使用して C++ とメッセージを共有できました。4.21.0 API バージョン以降では、Python と C++ 間でのメッセージ共有はデフォルトのインストールではサポートされていません。Protobuf Python API の 4.x 以降のバージョンでこの機能を有効にするには、環境変数 PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp
を定義し、Python/C++ 拡張機能がインストールされていることを確認してください。