Протобуф-сообщения

В версии 14.0.0 клиентской библиотеки Python представлен новый обязательный параметр конфигурации с именем use_proto_plus , который указывает, хотите ли вы, чтобы библиотека возвращала сообщения proto-plus или сообщения protobuf . Подробную информацию о том, как установить этот параметр, смотрите в документации по конфигурации .

В этом разделе описываются последствия выбора типа сообщений для производительности, поэтому мы рекомендуем вам прочитать и понять варианты, чтобы принять обоснованное решение. Однако, если вы хотите выполнить обновление до версии 14.0.0 без внесения изменений в код, вы можете установить для use_proto_plus значение True , чтобы избежать нарушения изменений интерфейса.

Сообщения Proto-plus и protobuf

В версии 10.0.0 клиентская библиотека Python перешла на новый конвейер генератора кода, который интегрировал proto-plus , чтобы улучшить эргономику интерфейса сообщений protobuf, сделав их более похожими на собственные объекты Python. Недостатком этого улучшения является то, что 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.

В анализе, который мы провели с момента выпуска версии 10.0.0 , мы определили, что время, затрачиваемое на эти преобразования типов, оказывает достаточно большое влияние на производительность, поэтому важно предоставить пользователям возможность использовать сообщения protobuf.

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

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

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

Прото-плюс сообщения
serialized = type(campaign).serialize(campaign)
deserialized = type(campaign).deserialize(serialized)
Протобуф-сообщения
serialized = campaign.SerializeToString()
deserialized = campaign.FromString(serialized)

Сериализация JSON

Прото-плюс сообщения
serialized = type(campaign).to_json(campaign)
deserialized = type(campaign).from_json(serialized)
Протобуф-сообщения
from google.protobuf.json_format import MessageToJson, Parse

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

Маски полей

Вспомогательный метод маски поля, предоставляемый api-core, предназначен для использования экземпляров сообщений protobuf. Поэтому при использовании сообщений proto-plus преобразуйте их в сообщения protobuf, чтобы использовать помощник:

Прото-плюс сообщения
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)
Протобуф-сообщения
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. Например:

Прото-плюс сообщения
val = client.get_type("CampaignStatusEnum").CampaignStatus.PAUSED
Протобуф-сообщения
val = client.get_type("CampaignStatusEnum").PAUSED

Чтобы упростить получение перечислений, в экземплярах GoogleAdsClient есть удобный атрибут, который имеет единообразный интерфейс независимо от того, какой тип сообщения вы используете:

val = client.enums.CampaignStatusEnum.PAUSED

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

Иногда полезно знать значение или идентификатор поля данного перечисления, например, PAUSED в CampaignStatusEnum соответствует 3 :

Прото-плюс сообщения
campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the value of campaign status
print(campaign.status.value)
Протобуф-сообщения
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 вам может потребоваться узнать, какому статусу кампании соответствует int 3 :

Прото-плюс сообщения
campaign = client.get_type("Campaign")
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# To read the name of campaign status
print(campaign.status.name)
Протобуф-сообщения
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 , интерфейс один и тот же независимо от типа сообщения:

Прото-плюс сообщения
ad.final_urls.append("https://www.example.com")
Протобуф-сообщения
ad.final_urls.append("https://www.example.com")

Сюда также входят все другие распространенные методы list , например, extend :

Прото-плюс сообщения
ad.final_urls.extend(["https://www.example.com", "https://www.example.com/2"])
Протобуф-сообщения
ad.final_urls.extend(["https://www.example.com", "https://www.example.com/2"])

Добавление типов сообщений к повторяющимся полям

Если повторяющееся поле не скалярного типа , поведение при добавлении их в повторяющиеся поля немного отличается:

Прото-плюс сообщения
frequency_cap = client.get_type("FrequencyCapEntry")
frequency_cap.cap = 100
campaign.frequency_caps.append(frequency_cap)
Протобуф-сообщения
# The add method initializes a message and adds it to the repeated field
frequency_cap = campaign.frequency_caps.add()
frequency_cap.cap = 100

Назначение повторяющихся полей

Как для скалярных, так и для нескалярных повторяющихся полей можно назначать списки полю разными способами:

Прото-плюс сообщения
# In proto-plus it's possible to use assignment.
urls = ["https://www.example.com"]
ad.final_urls = urls
Протобуф-сообщения
# 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

Пустые сообщения

Иногда полезно знать, содержит ли экземпляр сообщения какую-либо информацию или установлены ли какие-либо его поля.

Прото-плюс сообщения
# When using proto-plus messages you can simply check the message for
# truthiness.
is_empty = bool(campaign)
is_empty = not campaign
Протобуф-сообщения
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 . Доступ к нему можно получить и программно.

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

python -m pip install gapic-generator

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

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

Присутствие на местах

Поскольку поля в экземплярах сообщений protobuf имеют значения по умолчанию, не всегда интуитивно понятно, установлено ли поле или нет.

Прото-плюс сообщения
# Use the "in" operator.
has_field = "name" in campaign
Протобуф-сообщения
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; однако получить к ним доступ просто, преобразовав сообщение proto-plus в его аналог 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())

Трекер проблем

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