Python 生成コード ガイド

Protocol Buffer コンパイラが任意のプロトコル定義に対して生成する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 になります。

パッケージ

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つのモードを定義します: explicitimplicit です。これらのそれぞれについて、以下のセクションで説明します。

明示的なプレゼンスを持つ単数フィールド

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 内のフィールドに直接値を割り当てます。すると、foobar フィールドができます。

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;
}

グループは requiredoptional、または 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_AVALUE_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 クラスには、通常のフィールドと同様に nameserial_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コールバックを呼び出します。独自のサービスを実装する場合は、この生成されたサービスをサブクラス化し、そのメソッドを適切に実装する必要があります。

FooServiceインターフェースをサブクラス化します。Protocol Bufferコンパイラは、Serviceのメソッドの実装を次のように自動的に生成します。

  • GetDescriptor: サービスのServiceDescriptorを返します。
  • CallMethod: 提供されたメソッド記述子に基づいて、どのメソッドが呼び出されているかを判断し、それを直接呼び出します。
  • GetRequestClassGetResponseClass: 指定されたメソッドの適切な型の要求または応答のクラスを返します。

スタブ

Protocol Buffer コンパイラは、すべてのサービスインターフェースの「スタブ」実装も生成します。これは、サービスを実装するサーバーにリクエストを送信したいクライアントによって使用されます。上記の Foo サービスの場合、スタブ実装 Foo_Stub が定義されます。

Foo_StubFooのサブクラスです。そのコンストラクタはRpcChannelをパラメータとして受け取ります。スタブは、チャネルのCallMethod()メソッドを呼び出すことによって、サービスの各メソッドを実装します。

Protocol Buffer ライブラリには RPC 実装は含まれていません。しかし、生成されたサービス クラスを任意の選択した RPC 実装に接続するために必要なすべてのツールが含まれています。RpcChannelRpcController の実装を提供するだけで済みます。

プラグイン挿入ポイント

Python コードジェネレーターの出力を拡張したいコードジェネレータープラグインは、指定された挿入ポイント名を使用して、次のタイプのコードを挿入できます。

  • imports: インポートステートメント。
  • module_scope: 最上位の宣言。

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++ 拡張機能がインストールされていることを確認してください。