Protobuf 消息

借助 use_proto_plus 配置参数,您可以指定库是返回 proto-plus 消息还是 protobuf 消息。如需详细了解如何设置此参数,请参阅配置文档。

本部分介绍了每种选项对性能的影响,以便您为应用选择最佳方法。

Proto-plus 与 protobuf 消息

代码生成工具流水线集成了 proto-plus,旨在通过使 protobuf 消息接口的行为更像标准 Python 对象来提高其人体工程学性能。不过,这意味着使用 proto-plus 会产生性能开销。

Proto-plus 性能

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 字段时,系统会将值从内置 Python str 类型转换为 string 类型,以便该值与 protobuf 运行时兼容。

执行这些类型转换所花费的时间对性能的影响非常大,因此您应根据应用的需求来决定是使用 proto-plus 还是 protobuf 消息。

proto-plus 和 protobuf 消息的应用场景

Proto-plus 消息使用情形
与 protobuf 消息相比,proto-plus 在人体工程学方面进行了多项改进,因此非常适合用于编写可维护且易读的代码。由于它们公开的是标准 Python 对象,因此更易于使用和理解。
Protobuf 消息使用情形
在对性能要求较高的应用场景中使用 protobuf,尤其是在需要快速处理大型报告或构建包含大量操作的 mutate 请求(例如使用 BatchJobService 或 OfflineUserDataJobService)的应用中。

动态切换消息类型

为应用选择合适的讯息类型后,您可能会发现,在特定工作流程中需要使用另一种类型。在这种情况下,您可以使用客户端库提供的实用程序在两种类型之间动态切换。使用与之前相同的 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 Ads 客户端库常见用例的主要区别。

字节序列化

Proto-plus 消息
serialized = type(campaign).serialize(campaign)
deserialized = type(campaign).deserialize(serialized)
Protobuf 消息
serialized = campaign.SerializeToString()
deserialized = campaign.FromString(serialized)

JSON 序列化

Proto-plus 消息
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-plus 消息
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-plus 消息
val = client.get_type("CampaignStatusEnum").CampaignStatus.PAUSED
Protobuf 消息
val = client.get_type("CampaignStatusEnum").PAUSED

为了简化枚举的检索,GoogleAdsClient 实例上有一个便捷属性,无论您使用哪种消息类型,该属性都具有一致的接口:

val = client.enums.CampaignStatusEnum.PAUSED

枚举值检索

有时,了解给定枚举的值或字段 ID 会很有用,例如 CampaignStatusEnum 上 PAUSED 对应于 3:

Proto-plus 消息
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
# Native protobuf enum fields already store the integer value (3):
print(campaign.status)
# Or look up the integer value from the enum name string:
print(status_enum.CampaignStatus.Value("PAUSED"))

枚举名称检索

有时,了解枚举字段的名称很有用。例如,从 API 读取对象时,您可能想知道整数 3 对应的广告系列状态:

Proto-plus 消息
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
print(status_enum.CampaignStatus.Name(campaign.status))

重复字段

如 proto-plus 文档中所述,重复字段通常等同于类型化列表,这意味着它们的行为几乎与 list 完全相同。

将值附加到重复的标量字段

向重复的标量类型字段(例如 string 或 int64 字段)添加值时,无论消息类型如何,界面都是相同的:

Proto-plus 消息
ad.final_urls.append("https://www.example.com")
Protobuf 消息
ad.final_urls.append("https://www.example.com")

这还包括所有其他常见的 list 方法,例如 extend:

Proto-plus 消息
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-plus 消息
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-plus 消息
# 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-plus 消息
# When using proto-plus messages you can check the message for truthiness.
is_empty = not bool(campaign)
Protobuf 消息
is_empty = campaign.ByteSize() == 0

消息复制

对于 proto-plus 和 protobuf 消息,请对 GoogleAdsClient 使用 copy_from 辅助方法:

client.copy_from(campaign, other_campaign)

空白消息字段

无论您使用哪种消息类型,设置空消息字段的过程都是相同的。您将一条空白消息复制到相关字段中。 另请参阅消息内容部分以及空消息字段指南。以下示例展示了如何设置空消息字段:

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

属于预留字的字段名称

使用 proto-plus 消息时,如果字段名称也是 Python 中的保留字,则该名称会自动显示为带有尾随下划线。以下示例展示了如何使用 Asset 实例:

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

预留名称的完整列表是在 GAPIC 生成器模块中构建的。也可以通过编程方式访问。

首先,安装模块:

python -m pip install gapic-generator

然后,在 Python REPL 或脚本中:

import gapic.utils

print(gapic.utils.reserved_names.RESERVED_NAMES)

字段存在性

由于 protobuf 消息实例上的字段具有默认值,因此很难直观地知道某个字段是否已设置。

Proto-plus 消息
# 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 方法,用于确定消息上的子消息、oneof 字段或 optional 标量字段是否已设置,即使已设置为默认值也是如此。(对 proto3 中的非可选标量字段调用 HasField 会引发 ValueError。)

Protobuf 消息方法

protobuf 消息接口包含一些不属于 proto-plus 接口的便捷方法;不过,您可以通过将 proto-plus 消息转换为其 protobuf 对等项来访问这些方法:

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

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

问题跟踪器

如果您对这些变更或迁移到库的最新版本有任何疑问或问题,请在问题跟踪器中提交问题。