Pydantic 动态字段:使用和不使用 `@computed_field` 的对比指南
- 安装 Pydantic
- 不使用 `@computed_field` 的实现
- 特性
 
- 使用 `@computed_field` 的实现
- 特性
 
- 使用和不使用 `@computed_field` 的对比
- 适用场景分析
- 什么时候不需要 `@computed_field`?
- 什么时候使用 `@computed_field`?
 
- 总结
 
在数据建模时,我们经常需要定义动态字段,基于模型中其他字段计算出结果。
 Pydantic 提供了一个专门的装饰器 @computed_field,用于声明这样的动态字段。但即使不使用 @computed_field,也能通过普通的 Python 属性实现类似的效果。
本文将通过两种实现方式的对比,帮助你理解何时选择 @computed_field,以及它的优势。
安装 Pydantic
确保已安装最新版本的 Pydantic:
pip install pydantic
不使用 @computed_field 的实现
 
我们可以通过普通的 Python @property 定义动态字段:
from pydantic import BaseModel, Fieldclass 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")@propertydef 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
特性
- 动态计算: total_tokens每次访问时都会重新计算值。
- 不会出现在序列化输出中: 默认情况下,@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_fieldclass 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()@propertydef 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}
特性
- 动态计算: 和 @property一样,每次访问时都会重新计算值。
- 自动出现在序列化输出中: @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?
 
- 只读属性: 如果动态字段只是为了在代码中访问,而不需要序列化输出。
- 灵活控制输出逻辑: 通过覆盖 .model_dump()方法手动添加动态字段。
例如:
class LLMUsageMetrics(BaseModel):input_tokens: int = Field(0)output_tokens: int = Field(0)@propertydef total_tokens(self) -> int:return self.input_tokens + self.output_tokensdef model_dump(self, *args, **kwargs):base_dict = super().model_dump(*args, **kwargs)base_dict['total_tokens'] = self.total_tokensreturn base_dictmetrics = LLMUsageMetrics(input_tokens=100, output_tokens=50)
print(metrics.model_dump())
# 输出: {'input_tokens': 100, 'output_tokens': 50, 'total_tokens': 150}
什么时候使用 @computed_field?
 
- 自动序列化输出: 动态字段需要出现在 .model_dump()和.model_dump_json()输出中。
- 清晰的字段文档: @computed_field支持额外的配置选项,如description和include_in_schema,提升可读性和可维护性。
总结
- 不使用 @computed_field可以满足简单的动态字段需求,但需要手动处理序列化输出逻辑。
- 使用 @computed_field能自动包含动态字段到序列化输出中,且提供更多配置选项,适合更复杂的场景。