Pythonで生成されたコードガイド

特定のプロトコル定義に対して、プロトコルバッファコンパイラがPython定義をどのように生成するかを正確に記述します。

proto2、proto3、およびEditionsで生成されたコード間の違いが強調されています。これらの違いは、このドキュメントで説明されている生成されたコードにおけるものであり、すべてのバージョンで同じである基本メッセージクラス/インターフェースにおけるものではありません。このドキュメントを読む前に、proto2言語ガイドproto3言語ガイド、および/またはEditionsガイドを読むことをお勧めします。

Python Protocol Buffersの実装は、C++やJavaとは少し異なります。Pythonでは、コンパイラは生成されたクラスの記述子を構築するためのコードのみを出力し、実際の作業はPythonメタクラスが行います。このドキュメントでは、メタクラスが適用された後に得られるものについて説明します。

コンパイラの呼び出し

プロトコルバッファコンパイラは、--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.protosrc/bar/baz.protoを読み取り、2つの出力ファイル: build/gen/foo_pb2.pybuild/gen/bar/baz_pb2.pyを生成します。コンパイラは必要に応じてディレクトリbuild/gen/barを自動的に作成しますが、buildbuild/genは**作成しません**。これらはすでに存在している必要があります。

Protocは、--pyi_outパラメーターを使用してPythonスタブ (.pyi) を生成できます。

.protoファイルまたはそのパスに、Pythonモジュール名で使用できない文字 (例えばハイフン) が含まれている場合、それらはアンダースコアに置き換えられることに注意してください。したがって、ファイルfoo-bar.protoはPythonファイルfoo_bar_pb2.pyになります。

パッケージ

プロトコルバッファコンパイラによって生成されるPythonコードは、.protoファイルで定義されたパッケージ名によって全く影響を受けません。代わりに、Pythonパッケージはディレクトリ構造によって識別されます。

メッセージ

単純なメッセージ宣言を考えます。

message Foo {}

プロトコルバッファコンパイラは、google.protobuf.Messageをサブクラス化するFooというクラスを生成します。このクラスは具体的なクラスであり、未実装の抽象メソッドは残されていません。C++やJavaとは異なり、Pythonで生成されたコードは.protoファイル内のoptimize_forオプションの影響を受けません。実際には、すべてのPythonコードはコードサイズのために最適化されています。

メッセージの名前がPythonキーワードと競合する場合、そのクラスは「Pythonキーワードと競合する名前」セクションで説明されているように、getattr()を介してのみアクセス可能になります。

独自のFooサブクラスを作成**すべきではありません**。生成されたクラスはサブクラス化を目的として設計されておらず、「脆い基底クラス」の問題につながる可能性があります。さらに、実装継承は悪い設計です。

Pythonメッセージクラスには、Messageインターフェースによって定義されたメンバーと、ネストされたフィールド、メッセージ、およびEnum型(下記参照)に対して生成されたメンバー以外には、特定のパブリックメンバーはありません。Messageは、メッセージ全体をチェック、操作、読み取り、書き込み(バイナリ文字列からのパースやバイナリ文字列へのシリアル化を含む)に使用できるメソッドを提供します。これらのメソッドに加えて、Fooクラスは以下の静的メソッドを定義します。

  • FromString(s): 指定された文字列からデシリアライズされた新しいメッセージインスタンスを返します。

また、テキスト形式でプロトコルメッセージを扱うためにtext_formatモジュールを使用することもできます。たとえば、Merge()メソッドを使用すると、メッセージのASCII表現を既存のメッセージにマージできます。

ネストされた型

メッセージは別のメッセージ内で宣言できます。例:

message Foo {
  message Bar {}
}

この場合、BarクラスはFooの静的メンバーとして宣言されるため、Foo.Barとして参照できます。

よく知られた型

プロトコルバッファは、独自のメッセージ型とともに.protoファイルで使用できるいくつかのよく知られた型を提供します。一部のWKTメッセージは、通常のプロトコルバッファメッセージメソッドに加えて特別なメソッドを持ちます。これは、google.protobuf.MessageとWKTクラスの両方をサブクラス化しているためです。

Any

Anyメッセージの場合、Pack()を呼び出して指定されたメッセージを現在のAnyメッセージにパックしたり、Unpack()を呼び出して現在のAnyメッセージを指定されたメッセージにアンパックしたりできます。例えば

any_message.Pack(message)
any_message.Unpack(message)

Unpack()はまた、渡されたメッセージオブジェクトの記述子と格納されている記述子をチェックし、一致しない場合はFalseを返し、アンパックを試みません。それ以外の場合はTrueを返します。

Is()メソッドを呼び出して、Anyメッセージが指定されたプロトコルバッファ型を表しているかを確認することもできます。例えば

assert any_message.Is(message.DESCRIPTOR)

内部メッセージのprotobuf型名を取得するには、TypeName()メソッドを使用します。

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オブジェクトとTimestamps間で変換するための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メッセージは、Timestampと同じメソッドを持ち、JSON文字列と他の時間単位の間で変換します。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で指定されたフィールドをソースからデスティネーションにマージします。

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内のフィールドに直接値を割り当てるだけで済みます。すると、ほら!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")

繰り返しフィールド

繰り返しフィールドには、スカラー、enum、メッセージの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)または区切りフィールド(editions)を使用してください。

グループは、ネストされたメッセージ型とフィールドを単一の宣言に結合し、メッセージに異なるワイヤー形式を使用します。生成されたメッセージはグループと同じ名前を持ちます。生成されたフィールドの名前は、グループ名の**小文字**です。

例えば、ワイヤー形式を除けば、次の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

未定義のキーの詳細については、次のセクションをご覧ください。

未定義のキーを参照する

Protocol Bufferのマップのセマンティクスは、未定義のキーに関しては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>}

これは、通常の埋め込みメッセージフィールドとは**異なります**。通常の埋め込みメッセージフィールドでは、メッセージ自体はフィールドのいずれかに値を割り当てた場合にのみ作成されます。

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が外側のスコープで定義されている場合、値はモジュール定数になります。メッセージ内に定義されている場合(上記のように)、それらはそのメッセージクラスの静的メンバーになります。

例えば、Proto内の以下の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 enumは、enum型に定義されている数値以外の値を含めることはできません。enumにない値を割り当てると、生成されたコードは例外をスローします。これはproto2のenumの動作と同等です。

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")

メッセージクラスにはWhichOneofメソッドもあり、oneof内のどのフィールド(もしあれば)が設定されているかを調べることができます。このメソッドは、設定されているフィールドの名前を返します。何も設定されていない場合はNoneを返します。

assert message.WhichOneof("test_oneof") is None
message.name = "Bender"
assert message.WhichOneof("test_oneof") == "name"

HasFieldClearFieldも、フィールド名に加えて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の組み込み関数getattr()setattr()を使用してのみアクセスでき、Pythonの通常の属性参照構文(つまりドット演算子)ではアクセスできません。

例えば、次の.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またはエディションのメッセージが与えられた場合

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: 指定されたメソッドに対して、正しい型の要求または応答のクラスを返します。

スタブ

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

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

プロトコルバッファライブラリにはRPC実装は含まれていません。ただし、生成されたサービスコードを、任意のRPC実装に接続するために必要なすべてのツールが含まれています。RpcChannelRpcControllerの実装を提供するだけで済みます。

プラグイン挿入ポイント

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

  • imports: インポートステートメント。
  • module_scope: トップレベルの宣言。

PythonとC++間でのメッセージの共有

Protobuf Python APIのバージョン4.21.0より前は、Pythonアプリケーションはネイティブ拡張機能を使用してC++とメッセージを共有できました。APIバージョン4.21.0以降、PythonとC++間のメッセージ共有はデフォルトのインストールではサポートされていません。Protobuf Python APIの4.x以降のバージョンでこの機能を有効にするには、環境変数PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cppを定義し、Python/C++拡張機能がインストールされていることを確認してください。