Pydantic TypeAdapter 类型适配器
- Authors
- @SLIPPERTOPIA
核心作用:
- 适配器:即类型转换,允许开发者对输入数据进行转换和层次化处理,即支持普通类型及嵌套类型的类型转换。
- 类型检验与序列化:可以用来对任意 Python 类型(包括非 BaseModel 类型)实现强类型校验和序列化。
适配器
TypeAdapter 最常用的一个作用就是类型转换,比如把 str
转换为 int
。 这种场景下,写一个列表推导式也能搞定,但 TypeAdapter 更简洁一些。
from pydantic import TypeAdapter
int_adapter = TypeAdapter(list[int])
print(int_adapter.validate_json('[1, 2, 3]'))
# [1, 2, 3]
再复杂一点的,对嵌套数据进行类型转换。常用的一个场景是从 API 侧接收数据,或者从数据库中读取原始数据,然后转换为已经定义好的模型。
class Hobby(BaseModel):
name: str
class User(BaseModel):
name: str
age: int
hobbies: list[Hobby]
user_adapter = TypeAdapter(User)
print(user_adapter.validate_json('{"name": "Alice", "age": "25", "hobbies": [{"name": "reading"}, {"name": "traveling"}]}'))
# User(name='Alice', age=25, hobbies=[Hobby(name='reading'), Hobby(name='traveling')])
原始数据中的 age 虽然是 str
类型,但通过 TypeAdapter 会自动将 str
转换为 int
。
Pydantic 中有很多 API 功能是相似的,比如上面这种写法,本质上就跟 User.model_validate_json(raw)
(以前叫 parse_raw
) 效果是一样的,这两种都是 Pydantic 中 JSON 解析的方法。官方博客《Building a product search API with GPT-4 Vision, Pydantic, and FastAPI》中介绍了如何结合 JSON 解析及 LLM 构造一个产品搜索 API。
TypeAdapter v.s., model_validate_json
官方关于 json parsing 这一块并没有明确推荐用哪个,但有一些简单的建议,比如不要重复创建 TypeAdapter 实例,通常情况下,直接使用 model_validate_json()
,而不是 model_validate(json.loads(...))
。
class SimpleUser(BaseModel):
name: str
age: int
class Hobby(BaseModel):
name: str
class ComplexUser(BaseModel):
name: str
age: int
hobbies: list[Hobby]
关于性能,我做了一个简单的测试(代码见:typeadapter-benchmark.py),对上面的 SimpleUser
和 ComplexUser
分别解析一百万次,结果如下:
- 对于简单的模型解析,model_validate_json 比 TypeAdapter 快 2 倍左右。
- 对于稍微复杂的模型解析,model_validate_json 比 TypeAdapter 快 1.6 倍左右。
从这个测试结果来看,model_validate_json 的性能比 TypeAdapter 好很多,如果项目中有大量 JSON 解析的需求,还是提前做一下性能测试,再决定使用哪个。
Case | TypeAdapter | model_validate_json | Speedup |
---|---|---|---|
SimpleUser | 1.4027 (s) | 0.6437 (s) | 2.18x |
ComplexUser | 2.1959 (s) | 1.3801 (s) | 1.60x |
Why TypeAdapter?
即然 model_validate_json 比较快,为什么还要用 TypeAdapter 呢?
哎,使用 model_validate_json 的前提是我们有一个基于 BaseModel 的模型,如果项目中已经定义的模型不是 BaseModel,而是 dataclass,那就只能使用 TypeAdapter 了。
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
类型校验
既然 TypeAdapter 可以做类型转换,那类型校验自然也是理所当然的,只要转换不成功,那必然是类型不匹配。
class User(BaseModel):
name: str
age: int
user_adapter = TypeAdapter(User)
users=[{"name": "Alice", "age": "25"}, {"name": "Bob", "age": "30"}]
for user in users:
try:
print(user_adapter.validate_python(user))
except ValidationError as e:
print(e)
validate_json
and validate_python
从上面的若干例子可以看出,TypeAdapter 中有 validate_json
和 validate_python
两个方法,目的都是一样的,差异在于 validate_json
作用于 JSON 字符串及 bytes 类型,而 validate_python
作用于 Python 对象,比如 list
、dict
。
还有一种比较常见的解析方式是 Model(**data)
,这种其实也是调用 validate_python
进行解析的。
# BaseModel 的定义
def __init__(self, /, **data: Any) -> None:
__tracebackhide__ = True
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
if self is not validated_self:
xxx
小结
对于类型转换及校验,TypeAdapter 提供了除 BaseModel 之外的另一种选择。 至于怎么选择,就看个人喜好了。
参考: