Python 生成コードガイド

Protocol Buffer コンパイラが任意のプロトコル定義に対して生成する Python 定義を正確に記述します。

proto2 と proto3 の生成コード間の違いが強調されています。これらの違いは、このドキュメントに記載されている生成コード内にあり、両バージョンで共通の基本メッセージクラス/インターフェースにはありません。このドキュメントを読む前に、proto2 言語ガイドおよび/またはproto3 言語ガイドを読む必要があります。

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

コンパイラの呼び出し

Protocol Buffer コンパイラは、--python_out= コマンドラインフラグを指定して呼び出されたときに Python 出力を生成します。--python_out= オプションのパラメータは、コンパイラが Python 出力を書き込むディレクトリです。コンパイラは、各 .proto ファイル入力に対して .py ファイルを作成します。出力ファイル名は、.proto ファイル名から2つの変更を加えて計算されます。

  • 拡張子 (.proto) は _pb2.py に置き換えられます。
  • proto パス (--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 のファイルを読み込み、build/gen/foo_pb2.pybuild/gen/bar/baz_pb2.py の2つの出力ファイルを生成します。コンパイラは必要に応じて build/gen/bar ディレクトリを自動的に作成しますが、buildbuild/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 インターフェースで定義されているもの、およびネストされたフィールド、メッセージ、Enum 型のために生成されるもの (以下に説明) 以外に、特定のパブリックメンバーを持ちません。Message は、メッセージ全体をチェック、操作、読み取り、書き込みするために使用できるメソッドを提供し、これにはバイナリ文字列からのパースやバイナリ文字列へのシリアライズも含まれます。これらのメソッドに加えて、Foo クラスは以下の静的メソッドを定義します。

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

text_format モジュールを使用して、テキスト形式のプロトコルメッセージを操作することもできることに注意してください。例えば、Merge() メソッドを使用すると、メッセージの ASCII 表現を既存のメッセージにマージできます。

ネストされた型

メッセージは別のメッセージの内部に宣言できます。例えば、

message Foo {
  message Bar {}
}

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

よく知られた型

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

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)

内部メッセージの Protocol Buffer 型名を取得するには、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 オブジェクトと Timestamp 間で変換するための 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 で指定されたフィールドをソースからデスティネーションにマージします。

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 が続くものです。例えば、フィールド optional int32 foo_bar = 5; が与えられた場合、コンパイラは定数 FOO_BAR_FIELD_NUMBER = 5 を生成します。

フィールド名が Python のキーワードである場合、そのプロパティは Python のキーワードと競合する名前 のセクションで説明されているように、getattr()setattr() を介してのみアクセスできます。

単一フィールド (proto2)

メッセージ型ではない単一の (optional または required) フィールド 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")

単一フィールド (proto3)

メッセージ型ではない単一のフィールド foo がある場合、通常のフィールドのように foo を操作できます。例えば、foo の型が int32 の場合、以下のように記述できます。

message.foo = 123
print(message.foo)

foo に誤った型の値を設定すると、TypeError が発生することに注意してください。

foo が設定されていない状態で読み取られる場合、その値はそのフィールドのデフォルト値になります。foo の値をクリアし、その型のデフォルト値にリセットするには、Message インターフェースの ClearField() メソッドを呼び出します。例えば、

message.foo = 123
message.ClearField("foo")

単一メッセージフィールド

メッセージ型は少し異なる動作をします。埋め込みメッセージフィールドに値を割り当てることはできません。代わりに、子メッセージ内の任意のフィールドに値を割り当てると、親のメッセージフィールドが設定されたことになります。また、親メッセージの HasField() メソッドを使用して、メッセージ型フィールドの値が設定されているかを確認することもできます。

例えば、以下の .proto 定義があるとします。

message Foo {
  optional Bar bar = 1;
}
message Bar {
  optional 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")

繰り返しフィールド

繰り返しフィールドには、スカラー、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() メソッドはメッセージのリスト全体を追加しますが、リスト内のすべてのメッセージのコピーを作成します。

例えば、このメッセージ定義が与えられた場合、

message Foo {
  repeated Bar bars = 1;
}
message Bar {
  optional int32 i = 1;
  optional 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)

グループは非推奨であり、新しいメッセージ型を作成する際には使用すべきではありません。代わりに、ネストされたメッセージ型を使用してください。

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

例えば、ワイヤー形式を除いて、以下の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>}

これは通常の埋め込みメッセージフィールドとは異なります。通常の埋め込みメッセージフィールドでは、メッセージ自体はフィールドの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;
  }
  optional 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() を介してのみアクセスできます。

Enum に設定できる値は、Protocol Buffers のバージョンによって異なります。

  • proto2 では、Enum はその Enum 型に定義されている数値以外の値を含めることはできません。Enum に存在しない値を割り当てると、生成されたコードは例外をスローします。
  • proto3 はオープン Enum セマンティクスを使用します。Enum フィールドには任意の int32 値を含めることができます。

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 のみ)

拡張範囲を持つメッセージが与えられた場合、

message Foo {
  extensions 100 to 199;
}

Foo に対応する Python クラスには、Extensions と呼ばれるメンバーがあり、これは拡張識別子を現在の値にマッピングする辞書です。

拡張定義が与えられた場合、

extend Foo {
  optional int32 bar = 123;
}

Protocol Buffer コンパイラは、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;

その場合、Protocol Buffer コンパイラは、このセクションで説明されているように、ファイル内で見つかったサービス定義に基づいてコードを生成します。しかし、生成されたコードは特定の RPC システムに結びついていないため、1つのシステムに特化したコードよりも多くの間接参照レベルが必要となるため、望ましくない場合があります。このコードが生成されないようにしたい場合は、この行をファイルに追加してください。

option py_generic_services = false;

上記のどちらの行も指定されていない場合、汎用サービスは非推奨であるため、オプションはデフォルトで false になります。(2.4.0 より前は、オプションのデフォルトは true であったことに注意してください)

.proto 言語サービス定義に基づいた RPC システムは、システムに適したコードを生成するためのプラグインを提供すべきです。これらのプラグインは、抽象サービスが無効になっていることを要求する可能性が高く、それによって同じ名前の独自のクラスを生成できます。プラグインはバージョン 2.3.0 (2010年1月) で導入されました。

本セクションの残りの部分では、抽象サービスが有効になっている場合に Protocol Buffer コンパイラが何を生成するかについて説明します。

インターフェース

サービス定義が与えられた場合、

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

Protocol Buffer コンパイラは、このサービスを表すクラス Foo を生成します。Foo には、サービス定義で定義された各メソッドに対応するメソッドがあります。この場合、メソッド Bar は次のように定義されます。

def Bar(self, rpc_controller, request, done)

パラメータは Service.CallMethod() のパラメータと同等ですが、method_descriptor 引数は暗黙的です。

これらの生成されたメソッドは、サブクラスによってオーバーライドされることを意図しています。デフォルトの実装は、メソッドが未実装であることを示すエラーメッセージとともに単に controller.SetFailed() を呼び出し、その後 done コールバックを呼び出します。独自のサービスを実装する場合、この生成されたサービスをサブクラス化し、そのメソッドを適切に実装する必要があります。

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

  • GetDescriptor: サービスの ServiceDescriptor を返します。
  • CallMethod: 提供されたメソッド記述子に基づいてどのメソッドが呼び出されているかを判断し、直接呼び出します。
  • GetRequestClass および GetResponseClass: 指定されたメソッドに対して、正しい型のリクエストまたはレスポンスのクラスを返します。

スタブ

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++ とメッセージを共有できました。API バージョン 4.21.0 以降では、デフォルトのインストールでは Python と C++ 間でのメッセージの共有はサポートされていません。Protobuf Python API のバージョン 4.x 以降でこの機能を有効にするには、環境変数 PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp を定義し、Python/C++ 拡張がインストールされていることを確認してください。