Protobuf メッセージ

Python クライアント ライブラリのバージョン 14.0.0 には、ライブラリが proto-plus メッセージprotobuf メッセージのどちらを返すかを指定する、use_proto_plus という新しい必須構成パラメータが導入されています。このパラメータの設定方法については、構成のドキュメントをご覧ください。

このセクションでは、使用するメッセージの種類を選択する際のパフォーマンスへの影響について説明します。情報に基づいた決定を下すために、オプションを読んで理解することをおすすめします。ただし、コードを変更せずにバージョン 14.0.0 にアップグレードする場合は、インターフェースの変更が中断しないように、use_proto_plusTrue に設定します。

proto-plus メッセージと protobuf メッセージ

バージョン 10.0.0 では、Python クライアント ライブラリが新しいコード生成パイプラインに移行されました。これは、ネイティブ Python オブジェクトのように動作させることで、protobuf メッセージ インターフェースのエルゴノミクスを改善する方法として、proto-plus を統合しました。この改善のトレードオフは、proto プラスではパフォーマンスのオーバーヘッドが発生することです。

proto+ パフォーマンス

proto-plus の主な利点の一つは、型マーシャリングと呼ばれるプロセスによって protobuf メッセージよく知られた型をネイティブ Python 型に変換できることです。

マーシャリングは、proto-plus メッセージ インスタンスでフィールドにアクセスしたときに発生します。具体的には、protobuf 定義でフィールドの読み取りまたは設定が行われた場合です。

syntax = "proto3";

message Dog {
  string name = 1;
}

この定義を proto-plus クラスに変換すると、次のようになります。

import proto

class Dog(proto.Message):
    name = proto.Field(proto.STRING, number=1)

その後、他の Python オブジェクトと同様に、Dog クラスを初期化して name フィールドにアクセスできます。

dog = Dog()
dog.name = "Scruffy"
print(dog.name)

name フィールドを読み取って設定すると、値は protobuf ランタイムとの互換性を維持するため、ネイティブの Python str 型から string 型に変換されます。

バージョン 10.0.0 のリリース以降の分析では、これらの型変換に費やす時間はパフォーマンスに大きな影響を与えるため、ユーザーが protobuf メッセージを使用できるようにすることが重要であると判断しました。

proto-plus メッセージと protobuf メッセージのユースケース

Proto+ メッセージのユースケース
proto-plus は protobuf メッセージと比べて、人間工学的にさまざまな点で改善されているので、保守が容易で読みやすいコードを記述する場合に最適です。ネイティブの Python オブジェクトを公開するため、使いやすく、理解しやすくなります。
Protobuf メッセージのユースケース
protobuf は、パフォーマンス重視のユースケースに使用します。特に、大規模なレポートを迅速に処理する必要があるアプリや、BatchJobServiceOfflineUserDataJobService など、多数のオペレーションで変更リクエストを構築するアプリで使用します。

動的に変化するメッセージ タイプ

アプリに適したメッセージ タイプを選択した後、特定のワークフローで他のタイプが必要になる場合があります。この場合、クライアント ライブラリに用意されているユーティリティを使用して、2 つのタイプを動的に切り替えるのは簡単です。上記と同じ Dog メッセージ クラスを使用します。

from google.ads.googleads import util

# Proto-plus message type
dog = Dog()

# Protobuf message type
dog = util.convert_proto_plus_to_protobuf(dog)

# Back to proto-plus message type
dog = util.convert_protobuf_to_proto_plus(dog)

Protobuf メッセージ インターフェースの違い

proto-plus インターフェースについては詳細ドキュメントをご覧ください。ここでは、Google 広告クライアント ライブラリの一般的なユースケースに影響する主な相違点について説明します。

バイトのシリアル化

proto+ メッセージ
serialized = type(campaign).serialize(campaign)
deserialized = type(campaign).deserialize(serialized)
Protobuf メッセージ
serialized = campaign.SerializeToString()
deserialized = campaign.FromString(serialized)

JSON のシリアル化

proto+ メッセージ
serialized = type(campaign).to_json(campaign)
deserialized = type(campaign).from_json(serialized)
Protobuf メッセージ
from google.protobuf.json_format import MessageToJson, Parse

serialized = MessageToJson(campaign)
deserialized = Parse(serialized, campaign)

フィールド マスク

api-core が提供するフィールド マスク ヘルパー メソッドは、protobuf メッセージ インスタンスを使用するように設計されています。そのため、proto-plus メッセージを使用する場合は、それを protobuf メッセージに変換してヘルパーを利用します。

proto+ メッセージ
from google.api_core.protobuf_helpers import field_mask

campaign = client.get_type("Campaign")
protobuf_campaign = util.convert_proto_plus_to_protobuf(campaign)
mask = field_mask(None, protobuf_campaign)
Protobuf メッセージ
from google.api_core.protobuf_helpers import field_mask

campaign = client.get_type("Campaign")
mask = field_mask(None, campaign)

列挙型

proto-plus メッセージによって公開される列挙型は、Python のネイティブ enum 型のインスタンスであるため、多くのコンビニエンス メソッドを継承します。

列挙型の取得

GoogleAdsClient.get_type メソッドを使用して列挙型を取得する場合、proto-plus メッセージと protobuf メッセージのどちらを使用するかによって、返されるメッセージは若干異なります。次に例を示します。

proto+ メッセージ
val = client.get_type("CampaignStatusEnum").CampaignStatus.PAUSED
Protobuf メッセージ
val = client.get_type("CampaignStatusEnum").PAUSED

列挙型の取得を簡素化するために、使用するメッセージ タイプに関係なく一貫したインターフェースを持つ GoogleAdsClient インスタンスにコンビニエンス属性があります。

val = client.enums.CampaignStatusEnum.PAUSED

列挙値の取得

特定の列挙型の値またはフィールド ID を知ると便利な場合があります。たとえば、CampaignStatusEnumPAUSED3 に対応します。

proto+ メッセージ
campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the value of campaign status
print(campaign.status.value)
Protobuf メッセージ
campaign = client.get_type("Campaign")
status_enum = client.enums.CampaignStatusEnum
campaign.status = status_enum.PAUSED
# To read the value of campaign status
print(status_enum.CampaignStatus.Value(campaign.status))

列挙型名の取得

列挙型フィールドの名前を知っておくと便利な場合があります。たとえば、API からオブジェクトを読み取るときに、整数 3 が対応するキャンペーン ステータスを確認したい場合があります。

proto+ メッセージ
campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the name of campaign status
print(campaign.status.name)
Protobuf メッセージ
campaign = client.get_type("Campaign")
status_enum = client.enums.CampaignStatusEnum
# Sets the campaign status to the int value for PAUSED
campaign.status = status_enum.PAUSED
# To read the name of campaign status
status_enum.CampaignStatus.Name(campaign.status)

繰り返しフィールド

proto-plus のドキュメントで説明されているように、繰り返しフィールドは一般的に型付きリストと同等です。つまり、list とほぼ同じように動作します。

繰り返しスカラー フィールドへの追加

繰り返しのスカラー型フィールド(string フィールドや int64 フィールドなど)に値を追加する場合、メッセージ タイプに関係なくインターフェースは同じです。

proto+ メッセージ
ad.final_urls.append("https://www.example.com")
Protobuf メッセージ
ad.final_urls.append("https://www.example.com")

これには、extend など、他のすべての一般的な list メソッドも含まれます。

proto+ メッセージ
ad.final_urls.extend(["https://www.example.com", "https://www.example.com/2"])
Protobuf メッセージ
ad.final_urls.extend(["https://www.example.com", "https://www.example.com/2"])

繰り返しフィールドにメッセージ タイプを追加する

繰り返しフィールドがスカラー型でない場合、繰り返しフィールドに追加する際の動作は若干異なります。

proto+ メッセージ
frequency_cap = client.get_type("FrequencyCapEntry")
frequency_cap.cap = 100
campaign.frequency_caps.append(frequency_cap)
Protobuf メッセージ
# The add method initializes a message and adds it to the repeated field
frequency_cap = campaign.frequency_caps.add()
frequency_cap.cap = 100

繰り返しフィールドの割り当て

スカラー繰り返しフィールドと非スカラー繰り返しフィールドの両方で、さまざまな方法でリストにリストを割り当てることができます。

proto+ メッセージ
# In proto-plus it's possible to use assignment.
urls = ["https://www.example.com"]
ad.final_urls = urls
Protobuf メッセージ
# Protobuf messages do not allow assignment, but you can replace the
# existing list using slice syntax.
urls = ["https://www.example.com"]
ad.final_urls[:] = urls

空のメッセージ

メッセージ インスタンスに情報が含まれているかどうか、またはなんらかのフィールドが設定されているかどうかを確認すると便利な場合があります。

proto+ メッセージ
# When using proto-plus messages you can simply check the message for
# truthiness.
is_empty = bool(campaign)
is_empty = not campaign
Protobuf メッセージ
is_empty = campaign.ByteSize() == 0

メッセージのコピー

proto-plus と protobuf の両方のメッセージには、GoogleAdsClientcopy_from ヘルパー メソッドを使用することをおすすめします。

client.copy_from(campaign, other_campaign)

メッセージ フィールドが空です

空のメッセージ フィールドを設定するプロセスは、使用するメッセージ タイプに関係なく同じです。対象のフィールドに空のメッセージをコピーします。メッセージのコピーセクションと空のメッセージ フィールドのガイドをご覧ください。空のメッセージ フィールドを設定する方法の例を次に示します。

client.copy_from(campaign.manual_cpm, client.get_type("ManualCpm"))

予約語であるフィールド名

proto プラス メッセージを使用する場合、フィールド名が Python の予約語でもあると、フィールド名は自動的に末尾にアンダースコア付きで表示されます。Asset インスタンスの操作例を次に示します。

asset = client.get_type("Asset")
asset.type_ = client.enums.AssetTypeEnum.IMAGE

予約済みの名前の完全なリストは、Gapic Generator モジュールで構築されています。プログラムからもアクセスできます。

まず、モジュールをインストールします。

python -m pip install gapic-generator

Python REPL またはスクリプトで次のようにします。

import gapic.utils
print(gapic.utils.reserved_names.RESERVED_NAMES)

フィールド プレゼンス

protobuf メッセージ インスタンスのフィールドにはデフォルト値があるため、フィールドが設定されているかどうかを常に直感的に把握できるとは限りません。

proto+ メッセージ
# Use the "in" operator.
has_field = "name" in campaign
Protobuf メッセージ
campaign = client.get_type("Campaign")
# Determines whether "name" is set and not just an empty string.
campaign.HasField("name")

protobuf の Message クラス インターフェースには HasField メソッドがあります。このメソッドでは、メッセージのフィールドがデフォルト値に設定されている場合でも、そのフィールドが設定されているかどうかを判別します。

Protobuf メッセージ メソッド

protobuf メッセージ インターフェースには、proto-plus インターフェースには含まれていない便利なメソッドがいくつか含まれていますが、protobuf メッセージを対応する protobuf メッセージに変換することで簡単にアクセスできます。

# Accessing the ListFields method
protobuf_campaign = util.convert_protobuf_to_proto_plus(campaign)
print(campaign.ListFields())

# Accessing the Clear method
protobuf_campaign = util.convert_protobuf_to_proto_plus(campaign)
print(campaign.Clear())

Issue Tracker

これらの変更についてご不明な点がある場合や、ライブラリのバージョン 14.0.0 への移行に問題がある場合は、トラッカーで問題を報告してください。