Сообщения 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)

Затем вы можете инициализировать класс Dog и получить доступ к его полю name, как и к любому другому объекту Python:

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

При чтении и настройке поля name значение преобразуется из встроенного типа Python str в тип string, чтобы оно было совместимо со средой выполнения protobuf.

Время, затрачиваемое на преобразование типов, достаточно велико, чтобы вы могли решить, исходя из потребностей вашего приложения, использовать ли сообщения proto-plus или protobuf.

Примеры использования сообщений proto-plus и protobuf

Примеры использования сообщений Proto-plus
Proto-plus предлагает ряд эргономических улучшений по сравнению с сообщениями protobuf, поэтому они идеально подходят для написания поддерживаемого и читаемого кода. Поскольку они предоставляют стандартные объекты 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 Рекламы.

Сериализация байтов

Сообщения 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, являются экземплярами встроенного типа enum Python и поэтому наследуют ряд удобных методов.

Получение типа перечисления

При использовании метода 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

Получение значения перечисления

Иногда полезно знать значение или идентификатор поля определенного перечисления. Например, PAUSED в CampaignStatusEnum соответствует 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 используйте вспомогательный метод copy_from в GoogleAdsClient:

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 generator. Доступ к нему можно получить и программным путем.

Сначала установите модуль:

python -m pip install gapic-generator

Затем в REPL или скрипте Python:

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, даже если оно имеет значение по умолчанию. Если вызвать HasField для необязательных скалярных полей в proto3, возникнет ошибка 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()

Система отслеживания ошибок

Если у вас есть вопросы об этих изменениях или проблемы с переходом на последнюю версию библиотеки, сообщите о проблеме в системе отслеживания ошибок.