Python中未知类型Protobuf消息的访问字段

2024-06-07 05:00:23 发布

您现在位置:Python中文网/ 问答频道 /正文

假设我有两个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来访问公共设置字段,例如:

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或任何未来类型的消息,只要公共字段仍位于索引3。

我的问题是:有没有办法做类似的事情Python?我一直在看Protobuf documentation,但找不到办法。


Tags: 方法消息类型messagecommon解决方案事情optional
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;
   }
}

这样,就不会有歧义:每次解析相同的proto(Parent)。


不管怎样,如果现在改变它已经太晚了,我建议您定义一个只有共享字段的新消息,比如

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

然后,您应该能够假设消息(或者A或者B)实际上是Shared,并相应地解析它。未知字段将不相关。

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

settings_value = getattr(obj, 'CommonSettings')

我也有类似的问题。

我所做的是创建一个新消息,使用枚举指定类型:

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

希望这有帮助。

相关问题 更多 >