12

假设我有 2 个 Protobuf 消息,A 和 B。它们的整体结构相似,但不完全相同。因此,我们将共享的内容移到了一个单独的消息中,我们称为 Common。这很好用。

但是,我现在面临以下问题:存在一种特殊情况,我必须处理序列化消息,但我不知道它是 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++ 中,我使用反射 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 ' 方法使用FindFieldByNumber()来获取我要获取的字段的描述符。然后它使用反射来获取实际数据。getCommonBlock可以处理类型 A、B 或任何未来类型的消息,只要 Common 字段位于索引 3 处。

我的问题是:有没有办法用 Python 做类似的事情?我一直在查看Protobuf 文档,但想不出办法。

4

4 回答 4

6

我知道这是一个旧线程,但无论如何我都会为后代做出回应:

首先,如您所知,仅从其序列化形式确定协议缓冲区消息的类型是不可能的。您可以访问的序列化表单中的唯一信息是字段编号及其序列化值。

其次,做到这一点的“正确”方法是拥有一个包含两者的原型,比如

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每次都解析相同的 proto ( )。


无论如何,如果改变它为时已晚,我建议你做的是定义一个只有共享字段的新消息,比如

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

然后,您应该能够假装消息(或AB)实际上是 a Shared,并相应地对其进行解析。未知字段将无关紧要。

于 2015-12-18T22:09:33.177 回答
0

与 C++ 等静态类型语言相比,Python 的优势之一是您不需要使用任何特殊的反射代码来获取未知类型对象的属性:您只需询问该对象。执行此操作的内置函数是getattr,因此您可以执行以下操作:

settings_value = getattr(obj, 'CommonSettings')
于 2013-05-17T11:59:42.310 回答
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 ...

希望这可以帮助。

于 2014-06-09T08:16:42.800 回答
0

如何以标头+有效负载格式“连接”两个协议缓冲区,例如,标头作为公共数据后跟消息 A 或 B,如protobuf 技术所建议的那样?

这就是我在 mqtt 消息中使用各种类型的有效负载作为 blob 的方式。

于 2015-01-23T22:19:50.177 回答