普通网友 2026-01-25 21:10 采纳率: 98.4%
浏览 0
已采纳

TypeError: Descriptors cannot be created directly — 常见于Protobuf 4+中误用message类实例化

在 Protobuf 4+(如 `protobuf>=4.21.0`)中,常见错误 `TypeError: Descriptors cannot be created directly` 多源于**直接调用生成的 message 类构造器时传入了未解析的原始字典或 JSON 数据**。例如:`User(name="Alice")` 正常,但 `User({"name": "Alice"})` 或 `User(**{"name": "Alice"})` 会触发该异常——因为 v4+ 废弃了动态 descriptor 构建机制,禁止运行时反射式创建 message 实例。根本原因是:Protobuf 现在强制要求所有 message 实例必须通过 `.FromString()`, `.ParseFrom()` 或显式字段赋值初始化,而非字典解包。典型场景包括 FastAPI 请求体解析、Pydantic 模型转 Protobuf、或误用 `dataclasses.asdict()` 后直接构造。解决方案:改用 `user = User(); user.name = "Alice"`,或先序列化为 bytes 再解析;若需字典映射,应借助 `google.protobuf.json_format.ParseDict()`。升级后务必检查所有 message 初始化逻辑。
  • 写回答

1条回答 默认 最新

  • 三月Moon 2026-01-25 21:10
    关注
    ```html

    一、现象层:错误表征与典型复现代码

    在 Protobuf v4.21.0+ 升级后,开发者频繁遭遇如下异常:

    TypeError: Descriptors cannot be created directly.
    This error may be raised when attempting to construct a proto message using a dict,
    e.g., User({"name": "Alice"}) or User(**{"name": "Alice"}).
    

    该错误并非语法错误,而是运行时 DescriptorPool 的强校验机制触发的保护性中断。它明确拒绝“运行时动态构造 descriptor 实例”的行为。

    二、机制层:Protobuf v4+ 的核心架构演进

    • Descriptor 静态化:v4+ 彻底移除了 _internal_create_keyGeneratedProtocolMessageType.__new__ 对字典参数的隐式支持;
    • DescriptorPool 硬隔离:所有 Message 类绑定的 DESCRIPTOR 必须在 import 时静态注册,禁止运行时注入;
    • 构造器契约收紧:生成类的 __init__ 仅接受显式字段关键字(name="Alice")、bytes、或已解析的 Message 实例。

    三、场景层:高频出错的 5 类真实用例

    场景错误写法风险根源
    FastAPI 请求体解析user = User(request_body_dict)依赖 Pydantic .dict() 后直传
    Pydantic → Protobuf 转换User(**pydantic_model.dict())忽略字段类型映射与嵌套 message 初始化
    dataclass 转 ProtobufUser(**asdict(dc_instance))未处理 repeated 字段的 list→repeated 转换
    单元测试 mock 构造User({"id": 1, "tags": ["a"]})测试代码沿用 v3 习惯未适配
    JSON API 响应反序列化User(json.loads(raw_json))跳过 json_format.ParseDict 校验流程

    四、解决方案层:三阶合规初始化路径

    1. 显式赋值法(推荐用于简单对象)
      user = User(); user.name = "Alice"; user.id = 123
    2. 二进制流解析法(高兼容性)
      user = User().FromString(User(name="Alice").SerializeToString())
    3. JSON 字典安全解析法(生产首选)
      from google.protobuf.json_format import ParseDict
      user = ParseDict({"name": "Alice", "id": 123}, User())

    五、工程实践层:自动化检测与迁移策略

    为保障大规模服务平滑升级,建议实施以下检查项:

    • 静态扫描:使用 grep -r "User([^)]*{[^}]*}" . --include="*.py" 定位高危调用;
    • CI 拦截:在 pre-commit hook 中集成 proto-init-checker 工具(开源可定制);
    • 封装适配层:定义统一转换函数 dict_to_proto(d: dict, msg_cls: Type[T]) -> T,内部强制走 ParseDict
    • FastAPI 集成:自定义 ProtobufBody 依赖项,自动完成 JSON→Proto 解析。

    六、原理验证层:Mermaid 流程图揭示执行路径差异

    flowchart TD A[User(**dict)] --> B{v3.x 分支} B -->|允许| C[动态 descriptor 构建] A --> D{v4.21.0+ 分支} D -->|拒绝| E[raise TypeError\nDescriptors cannot be created directly] F[User(); u.name = ...] --> G[直接字段赋值\n绕过 descriptor 构造] H[ParseDict(dict, User())] --> I[经 json_format 校验\n字段存在性/类型/嵌套合法性]
    ```
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 今天
  • 创建了问题 1月25日