Python之将Protobuf消息转换为字典

部署运行你感兴趣的模型镜像

为什么要这么做?

  1. 类型安全
    方法开始时检查输入是否为 protobuf Message 类型,防止错误输入
    
    检查字段是否存在,避免处理不完整的数据
  2. 全面处理各种字段类型
    普通标量字段: 直接转换
    
    枚举字段: 将数字值转换为可读的字符串
    
    repeated 字段: 正确处理列表类型
    
    嵌套消息: 递归处理
    
    map 字段: 保持键值对结构
  3. 可读性增强
    枚举值转换为字符串形式,更易读
    
    嵌套结构展开为 Python 原生字典,便于理解
  4. 兼容性
    处理了可能出现的异常情况(如无效枚举值)
    
    保留了原始值作为后备,不会因为转换失败而丢失数据
  5. 一致性
    无论 protobuf 消息结构如何复杂,都能转换为统一的字典格式
    
    便于后续的 JSON 序列化或其它处理

目录结构

        
文件拆解

example.proto
syntax = "proto3";

package example;

enum CrossRewardItemStatus {
  CRIS_None = 0; // 不可领取状态
  CRIS_NotClaimed = 1; // 可领取状态
  CRIS_Claimed = 2; // 已领取状态
}

message ActivityCross {
  repeated CrossRewardItemStatus items = 1; // 奖励状态
}
protobuf2dict/members.py
from google.protobuf import __version__
from packaging.version import Version
from google.protobuf.internal import api_implementation
from google.protobuf.internal.containers import *
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from google.protobuf.message import Message

pb_version = Version(__version__)

major = pb_version.major
minor = pb_version.minor
micro = pb_version.micro

if api_implementation.Type() == 'cpp':
    raise RuntimeError("需要运行check脚本更换兼容arm架构的protobuf")
else:
    from google.protobuf.descriptor import EnumValueDescriptor, EnumDescriptor

types_enum = (
    EnumValueDescriptor,
    EnumTypeWrapper,
    EnumDescriptor,
)

types_list = (
    RepeatedScalarFieldContainer,
    RepeatedCompositeFieldContainer,
    BaseContainer
)

types_dict = (
    MessageMap,
    ScalarMap,
)



__all__ = ['Message',
           'types_dict', 'types_list', 'types_enum',
           'major', 'minor', 'micro',
           ]
protobuf2dict/pb2dict.py
from .members import types_dict, types_enum, types_list, Message, EnumValueDescriptor
from google.protobuf import descriptor as _descriptor


class ProtobufToDict:
    __slots__ = ()

    @classmethod
    def pb_to_dict_new(cls, pb):
        if not isinstance(pb, Message):
            raise TypeError("pb must be protobuf Message type")
        result_dict = {}
        descriptor = pb.DESCRIPTOR
        for field in descriptor.fields:
            field_name = field.name
            field_value = getattr(pb, field_name, None)
            if field_value is None:
                raise RuntimeError(f"未在{pb.__class__.__name__}中找到字段{field_name}")

            if isinstance(field_value, Message) and not pb.HasField(field_name):
                # if not pb.HasField(field_name):
                result_dict[field.name] = None
                continue

            # 处理 repeated 字段
            if field.label == _descriptor.FieldDescriptor.LABEL_REPEATED:
                result_dict[field.name] = cls._handle_list(field_value)
                continue

            # 处理枚举字段
            if field.type == _descriptor.FieldDescriptor.TYPE_ENUM:
                if isinstance(field_value, list):  # 防止重复字段被错误处理为枚举
                    result_dict[field.name] = field_value
                    continue
                field_value = field.enum_type.values_by_number[field_value]
                result_dict[field.name] = cls._handle_type(field_value)
                continue

            # 处理普通字段
            result_dict[field.name] = cls._handle_type(field_value)

        return result_dict

    @classmethod
    def _handle_type(cls, value):
        if isinstance(value, types_list):
            return cls._handle_list(value)
        elif isinstance(value, types_dict):
            return cls._handle_dict(value)
        elif isinstance(value, types_enum):
            return cls._handle_enum(value)
        elif isinstance(value, Message):
            return cls.pb_to_dict(value)
        else:
            return value

    @classmethod
    def _handle_list(cls, field_value):
        result = []
        for value in field_value:
            result.append(cls._handle_type(value))
        return result

    @classmethod
    def _handle_dict(cls, field_value):
        result_dict = {}
        for key, value in field_value.items():
            result_dict[key] = cls._handle_type(value)
        return result_dict

    @classmethod
    def _handle_enum(cls, field_value):
        result_dict = {}
        if hasattr(field_value, '__members__'):
            for key in getattr(field_value, "__members__"):
                value = getattr(field_value, key).value
                result_dict[key] = cls._handle_type(value)
        elif isinstance(field_value, EnumValueDescriptor):
            result_dict[field_value.name] = cls._handle_type(field_value.number)
        else:
            raise TypeError(f"Unsupported enum type: {type(field_value)}")
        return result_dict

pb2dict = ProtobufToDict.pb_to_dict

protobuf2dict/__init__.py

from .pb2dict import ProtobufToDict, pb2dict
requirements.txt(依赖文件,通过pip install -r requirement.txt安装)
packaging @ file:///C:/b/abs_3by6s2fa66/croot/packaging_1734472138782/work
protobuf==4.25.3
main.py(主程序入口)

from generated.example_pb2 import ActivityCross, CrossRewardItemStatus
from protobuf2dict import pb2dict

if __name__ == '__main__':

    activity = ActivityCross()

    activity.items.append(CrossRewardItemStatus.CRIS_None)
    activity.items.append(CrossRewardItemStatus.CRIS_None)
    activity.items.append(CrossRewardItemStatus.CRIS_None)
    activity.items.append(CrossRewardItemStatus.CRIS_None)
    activity.items.append(CrossRewardItemStatus.CRIS_None)

    x = pb2dict(activity)
    print(x)

注意事项

对于example.proto中items的类型为枚举类型的数组(既被定义为enum又被定义为repeated)

在pb2dict方法中需要先判断字段是否是repeated类型再去判断enum类型

如果只有对enum类型的判断,那么这里的字段items你会发现也会被匹配上

但是item的值是数组(列表),无法被作为key来查找值的

因此也经常由于类型判断缺失导致repeated被误认为enum而报错

您可能感兴趣的与本文相关的镜像

Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值