【问题标题】:Accessing field of Protobuf message of unknown type in Python在 Python 中访问未知类型的 Protobuf 消息的字段
【发布时间】:2013-05-17 10:43:08
【问题描述】:

假设我有 2 个 Protobuf-Messages,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 documentation,但想不出办法。

【问题讨论】:

    标签: python reflection protocol-buffers


    【解决方案1】:

    我知道这是一个旧线程,但我还是会为后代做出回应:

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

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

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

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

    【讨论】:

      【解决方案2】:

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

      settings_value = getattr(obj, 'CommonSettings')
      

      【讨论】:

      • 如果我有一个 obj 的实例,那就可以了。也许我应该稍微澄清一下我的问题:我收到消息 - 以典型的 protobuf 方式 - 作为序列化的 blob(二进制内存流)。如何在不知道底层消息类型的情况下从中实例化 obj?
      【解决方案3】:

      我也遇到过类似的问题。

      我所做的是创建一条新消息,其中包含一个指定类型的枚举:

      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”消息,否则如果您最近添加字段,您将无法实现二进制兼容(因为您也必须转移 inheriting 消息字段)。

      这样,您就可以收到一条通用消息:

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

      希望这会有所帮助。

      【讨论】:

      • 我在消息 A 和 B 中看到“Type”。你不打算在那些地方输入“Base”吗?
      • 如果导入正确,TYPE是protobuf文件中定义的枚举,可以直接调用。
      • 如另一个答案中所述,使用 oneof,因为在 protobuf 级别本机为您做类似的事情。您可以使用不同语言的 API 查询 oneof 字段的类型。
      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-12-22
        • 1970-01-01
        • 2023-01-09
        • 1970-01-01
        • 2018-06-12
        • 2022-01-26
        • 1970-01-01
        相关资源
        最近更新 更多