2013-05-17 15 views
7

Załóżmy, że mam 2 komunikaty Protobuf, A i B. Ich ogólna struktura jest podobna, ale nie identyczna. Przenieśliśmy udostępnione rzeczy do oddzielnej wiadomości, którą nazwaliśmy "Wspólne". To działa pięknie.Dostęp do pola komunikatu Protobuf nieznanego typu w Pythonie

Jednakże mam teraz następujący problem: istnieje specjalny przypadek, w którym muszę przetworzyć zserializowaną wiadomość, ale nie wiem, czy jest to wiadomość typu A czy typu B. Mam działające rozwiązanie w C++ (pokazanym poniżej), ale nie udało mi się znaleźć sposobu na zrobienie tego samego w Pythonie.

Przykład:

// 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 ... 
} 

Rozwiązanie Praca w C++

w C++ Używam odbicia API, aby uzyskać dostęp do pola CommonSettings takiego:

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)); 
} 

Metoda "getCommonBlock 'us es FindFieldByNumber(), aby uzyskać deskryptor pola, które próbuję zdobyć. Następnie wykorzystuje odbicie do pobrania rzeczywistych danych. getCommonBlock może przetwarzać wiadomości typu A, B lub dowolnego przyszłego typu, o ile pole wspólne pozostaje zlokalizowane w indeksie 3.

Moje pytanie brzmi: czy istnieje sposób na podobną rzecz Python? Patrzyłem na Protobuf documentation, ale nie mogłem znaleźć sposobu, aby to zrobić.

Odpowiedz

3

Wiem, że to stary wątek, ale ja i tak reagować dla potomności:

Po pierwsze, jak pan wie, że nie jest możliwe określenie typu wiadomości buforowej protokół wyłącznie od jego odcinkach formie. Jedyną informacją w postaci zserializowanej, do której masz dostęp, są numery pól i ich wartości serializowane.

Po drugie, „prawo” sposób to zrobić byłoby mieć proto który zawiera zarówno, jak

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; 
    } 
} 

ten sposób nie ma dwuznaczności: wystarczy przeanalizować ten sam Proto (Parent) za każdym razem, .


W każdym razie, jeśli jest zbyt późno, aby zmienić to, co polecam zrobić to zdefiniować nową wiadomość tylko wspólnych dziedzinach, jak

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

Należy wtedy w stanie udawać, że wiadomość (albo A lub B) jest w rzeczywistości Shared i odpowiednio ją przeanalizować. Nieznane pola będą nieistotne.

0

Jedną z zalet języka Python w porównaniu ze statycznie napisanym językiem, takim jak C++, jest to, że nie trzeba używać specjalnego kodu odbicia, aby uzyskać atrybut obiektu nieznanego typu: wystarczy zapytać obiekt. Wbudowana funkcja, która robi to getattr, więc można zrobić:

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

To by działało, gdybym miał instancję obj. Może powinienem trochę wyjaśnić swój problem: otrzymuję komunikat - w typowym stylu protobuf - jako serializowany obiekt typu blob (strumień binarnej pamięci). Jak utworzyć instancję obj z tego bez znajomości podstawowego typu komunikatu? – djf

0

miałem podobny problem.

Co zrobiłem było utworzyć nową wiadomość, a wyliczenia określający typ:

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

Następnie należy utworzyć konkretne rodzaje wiadomości:

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

I:

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

Pamiętaj, aby poprawnie zdefiniować komunikat "Base" lub nie będziesz kompatybilny z binariami, jeśli dodasz pola ostatnio (ponieważ będziesz musiał przesunięcie dziedziczące również pola komunikatu).

ten sposób można recive rodzajowe wiadomość:

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 ... 

nadzieję, że to pomaga.

+0

Widzę "Typ" w komunikacie A i B. Czy nie powinieneś wstawiać "Base" w tych miejscach? – deddebme

+0

Jeśli zaimportowano poprawnie, TYPE jest wylicznikiem zdefiniowanym w pliku protobuf, dzięki czemu można go wywołać bezpośrednio. – kraptor

0

Jak o "łączeniu" dwóch buforów protokołów w formacie nagłówka + ładunku, np. nagłówek jako wspólne dane następuje po wiadomości A lub B zgodnie z sugestią protobuf techniques?

Tak to zrobiłem z różnymi typami ładunku jako blob w wiadomości mqtt.

Powiązane problemy