2013-05-17 2 views
7

Предположим, у меня есть 2 Protobuf-сообщения, A и B. Их общая структура похожа, но не идентична. Поэтому мы переместили общий материал в отдельное сообщение, которое мы назвали Common. Это прекрасно работает.Доступ к полю сообщения Protobuf неизвестного типа в Python

Однако я столкнулся со следующей проблемой: существует специальный случай, когда мне приходится обрабатывать сериализованное сообщение, но я не знаю, является ли это сообщением типа A или типа B. У меня есть рабочее решение в C++ (показано ниже), но мне не удалось найти способ сделать то же самое в Python.

Пример:

// file: Common.proto 
// contains some kind of shared struct that is used by all messages: 
message Common { 
... 
} 

// file: A.proto 
import "Common.proto"; 

message A { 
    required int32 FormatVersion    = 1; 
    optional bool SomeFlag [default = true] = 2; 
    optional Common CommonSettings   = 3; 

    ... A-specific Fields ... 
} 

// file: B.proto 
import "Common.proto"; 

message B { 
    required int32 FormatVersion    = 1; 
    optional bool SomeFlag [default = true] = 2; 
    optional Common CommonSettings   = 3; 

    ... B-specific Fields ... 
} 

Рабочий раствор в C++

В C++ Я использую Reflection API, чтобы получить доступ к полю CommonSettings так:

namespace gp = google::protobuf; 
... 
Common* getCommonBlock(gp::Message* paMessage) 
{ 
    gp::Message* paMessage = new gp::Message(); 
    gp::FieldDescriptor* paFieldDescriptor = paMessage->GetDescriptor()->FindFieldByNumber(3); 
    gp::Reflection* paReflection = paMessage->GetReflection(); 
    return dynamic_cast<Common&>(paReflection->GetMessage(*paMessage,paFieldDescriptor)); 
} 

Метод 'getCommonBlock' us es FindFieldByNumber(), чтобы получить дескриптор поля, которое я пытаюсь получить. Затем он использует отражение для получения фактических данных. getCommonBlock может обрабатывать сообщения типа A, B или любого будущего типа, если общее поле остается в индексе 3.

Мой вопрос: есть ли способ сделать аналогичную вещь Python? Я смотрел на Protobuf documentation, но не мог понять, как это сделать.

ответ

3

Я знаю, что это старый нить, но я буду отвечать в любом случае для потомства:

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

Во-вторых, «правильный» способ сделать это будет иметь прото, который содержит как, например

message Parent { 
    required int32 FormatVersion    = 1; 
    optional bool SomeFlag [default = true] = 2; 
    optional Common CommonSettings   = 3; 

    oneof letters_of_alphabet { 
     A a_specific = 4; 
     B b_specific = 5; 
    } 
} 

Таким образом, нет никакой двусмысленности: вы просто разобрать тот же прото (Parent) каждый раз, когда ,


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

message Shared { 
    required int32 FormatVersion    = 1; 
    optional bool SomeFlag [default = true] = 2; 
    optional Common CommonSettings   = 3; 
} 

тогда Вы должны быть в состоянии сделать вид, что сообщение (либо A, либо B) на самом деле является Shared, и проанализируйте его соответствующим образом. Неизвестные поля будут неактуальны.

0

Одним из преимуществ Python над статически типизированным языком, таким как C++, является то, что вам не нужно использовать какой-либо специальный код отражения для получения атрибута объекта неизвестного типа: вы просто запрашиваете объект. Встроенная функция, которая делает это getattr, так что вы можете сделать:

settings_value = getattr(obj, 'CommonSettings') 
+5

Это сработало бы, если бы у меня был экземпляр obj. Может быть, мне нужно немного разъяснить мою проблему: я получаю сообщение - в типичной форме protobuf - как сериализованный blob (поток двоичной памяти). Как я могу создать экземпляр obj из этого, не зная тип базового сообщения? – djf

0

У меня была аналогичная проблема.

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

enum TYPE { 
    A = 0; 
    B = 1; 
} 
message Base { 
    required TYPE type = 1; 
    ... Other common fields ... 
} 

Затем создать определенные типы сообщений:

message A { 
    required TYPE type = 1 [default: A]; 
    ... other A fields ... 
} 

И:

message B { 
    required TYPE type = 1 [default: B]; 
    ... other B fields ... 
} 

Не забудьте правильно определить сообщение «Base», или вы не будете бинарно совместимы, если вы добавите поля в последнее время (так как вам придется сдвиг наследующий поля сообщений тоже).

Таким образом, вы можете Получать общее сообщение:

msg = ... receive message from net ... 

# detect message type 
packet = Base() 
packet.ParseFromString(msg) 

# check for type 
if packet.type == TYPE.A: 
    # parse message as appropriate type 
    packet = A() 
    packet.ParseFromString(msg) 
else: 
    # this is a B message... or whatever 

# ... continue with your business logic ... 

Надеется, что это помогает.

+0

Я вижу «Тип» в сообщениях A и B. Не полагаете ли вы помещать «Базу» в эти места? – deddebme

+0

Если вы импортировали правильно, TYPE - это перечисление, определенное в файле protobuf, чтобы вы могли его вызвать напрямую. – kraptor

0

Как насчет «конкатенирования» двух буферов протокола в формате заголовка + полезной нагрузки, например. как общие данные следует либо по сообщению A или B, как предложено protobuf techniques?

Так я сделал это с различными видами полезной нагрузки, как blob в сообщении mqtt.

Смежные вопросы