Pydantic 动态字段:使用和不使用 `@computed_field` 的对比指南
在数据建模时,我们经常需要定义动态字段,基于模型中其他字段计算出结果。
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
特性
- 动态计算:
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_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}
特性
- 动态计算: 和
@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)
@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
?
- 自动序列化输出: 动态字段需要出现在
.model_dump()
和.model_dump_json()
输出中。 - 清晰的字段文档:
@computed_field
支持额外的配置选项,如description
和include_in_schema
,提升可读性和可维护性。
总结
- 不使用
@computed_field
可以满足简单的动态字段需求,但需要手动处理序列化输出逻辑。 - 使用
@computed_field
能自动包含动态字段到序列化输出中,且提供更多配置选项,适合更复杂的场景。