Pydantic 动态字段:使用和不使用 `@computed_field` 的对比指南

发布于:2024-12-18 ⋅ 阅读:(7) ⋅ 点赞:(0)

在数据建模时,我们经常需要定义动态字段,基于模型中其他字段计算出结果。
Pydantic 提供了一个专门的装饰器 @computed_field,用于声明这样的动态字段。但即使不使用 @computed_field,也能通过普通的 Python 属性实现类似的效果。

本文将通过两种实现方式的对比,帮助你理解何时选择 @computed_field,以及它的优势。


安装 Pydantic

确保已安装最新版本的 Pydantic:

pip install pydantic

不使用 @computed_field 的实现

我们可以通过普通的 Python @property 定义动态字段:

from pydantic import BaseModel, Field

class LLMUsageMetrics(BaseModel):
    """LLM request usage metrics."""

    input_tokens: int = Field(0, description="Used input tokens by the request")
    output_tokens: int = Field(0, description="Used output tokens by the request")

    @property
    def total_tokens(self) -> int:
        """Total tokens used by the request."""
        return self.input_tokens + self.output_tokens

# 测试
metrics = LLMUsageMetrics(input_tokens=100, output_tokens=50)
print(metrics.total_tokens)  # 输出: 150

特性

  1. 动态计算: total_tokens 每次访问时都会重新计算值。
  2. 不会出现在序列化输出中: 默认情况下,@property 定义的字段不会包含在 .model_dump().model_dump_json() 输出中。
print(metrics.model_dump())
# 输出: {'input_tokens': 100, 'output_tokens': 50}  # 没有 total_tokens

如果想将其包含在输出中,可以手动在模型方法中添加逻辑。


使用 @computed_field 的实现

Pydantic 的 @computed_field 是专为动态字段设计的:

from pydantic import BaseModel, Field, computed_field

class LLMUsageMetrics(BaseModel):
    """LLM request usage metrics."""

    input_tokens: int = Field(0, description="Used input tokens by the request")
    output_tokens: int = Field(0, description="Used output tokens by the request")

    @computed_field()
    @property
    def total_tokens(self) -> int:
        """Total tokens used by the request."""
        return self.input_tokens + self.output_tokens

# 测试
metrics = LLMUsageMetrics(input_tokens=100, output_tokens=50)
print(metrics.total_tokens)  # 输出: 150
print(metrics.model_dump())
# 输出: {'input_tokens': 100, 'output_tokens': 50, 'total_tokens': 150}

特性

  1. 动态计算:@property 一样,每次访问时都会重新计算值。
  2. 自动出现在序列化输出中: @computed_field 定义的字段会自动包含在 .model_dump().model_dump_json() 输出中。

使用和不使用 @computed_field 的对比

特性 不使用 @computed_field 使用 @computed_field
定义方式 使用普通的 @property 使用 @computed_field@property 结合
动态计算 每次访问时动态计算 每次访问时动态计算
序列化输出 默认不包含在 .model_dump().model_dump_json() 自动包含在 .model_dump().model_dump_json()
使用场景 需要简单动态字段,但不需要出现在序列化输出中 需要动态字段且希望在序列化中体现
额外配置选项 不支持 支持 include_in_schema 等选项控制序列化行为

适用场景分析

什么时候不需要 @computed_field

  1. 只读属性: 如果动态字段只是为了在代码中访问,而不需要序列化输出。
  2. 灵活控制输出逻辑: 通过覆盖 .model_dump() 方法手动添加动态字段。

例如:

class LLMUsageMetrics(BaseModel):
    input_tokens: int = Field(0)
    output_tokens: int = Field(0)

    @property
    def total_tokens(self) -> int:
        return self.input_tokens + self.output_tokens

    def model_dump(self, *args, **kwargs):
        base_dict = super().model_dump(*args, **kwargs)
        base_dict['total_tokens'] = self.total_tokens
        return base_dict

metrics = LLMUsageMetrics(input_tokens=100, output_tokens=50)
print(metrics.model_dump())
# 输出: {'input_tokens': 100, 'output_tokens': 50, 'total_tokens': 150}

什么时候使用 @computed_field

  1. 自动序列化输出: 动态字段需要出现在 .model_dump().model_dump_json() 输出中。
  2. 清晰的字段文档: @computed_field 支持额外的配置选项,如 descriptioninclude_in_schema,提升可读性和可维护性。

总结

  1. 不使用 @computed_field 可以满足简单的动态字段需求,但需要手动处理序列化输出逻辑。
  2. 使用 @computed_field 能自动包含动态字段到序列化输出中,且提供更多配置选项,适合更复杂的场景。