← blog 技术原理与实践 · 2026-08-13

Python 库 pydantic 详解与实践:从核心概念到项目落地

深入解析 Pydantic v2:Rust 内核的校验器、BaseModel 与字段约束、四种 field_validator 模式、模型级验证、ConfigDict 配置,以及在一个 FastAPI LLM 网关项目中的实际应用。

20 min read

一、什么是 Pydantic?

Pydantic 是 Python 中使用最广泛的数据验证库,核心思路是:用类型注解(type hints)定义数据应该长什么样,然后自动完成验证、解析和序列化。

  • 验证核心由 Rust 编写(pydantic-core),性能极快
  • 每月下载量超过 5.5 亿次,PyPI 上约 8000 个包依赖它(FastAPI、Hugging Face、SQLModel、LangChain 等)
  • 无需手写任何验证逻辑,兼容 IDE 和静态分析工具

一句话总结:Pydantic 帮你”管数据、查错、自动转格式”,靠 Python 自带的类型注解干活。

二、安装

pip install pydantic

若需配置管理能力,额外安装:

pip install pydantic-settings

三、核心概念:BaseModel

模型(Model)就是继承 BaseModel 的类,字段用类型注解声明:

from datetime import datetime
from pydantic import BaseModel, PositiveInt

class User(BaseModel):
    id: int                                # 必填字段
    name: str = 'John Doe'                 # 有默认值,可选
    signup_ts: datetime | None             # 可为 None 的 datetime
    tastes: dict[str, PositiveInt]         # 嵌套类型:dict[str, int] 且值 > 0

# 传入"脏数据"(字符串、bytes、非标准格式)
external_data = {
    'id': '123',                           # 字符串 → 自动转 int
    'signup_ts': '2019-06-01 12:22',       # ISO 字符串 → 自动转 datetime
    'tastes': {'wine': 9, b'cheese': 7, 'cabbage': '1'},
}

user = User(**external_data)
print(user.id)          # 123
print(user.model_dump())
# {'id': 123, 'name': 'John Doe', 'signup_ts': datetime(...),
#  'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1}}

关键点:

  • 初始化即验证,验证通过才生成实例
  • 宽松模式(lax)下会自动强制类型转换('123'→123,b'cheese'→'cheese'),这是刻意的设计
  • 验证失败抛 ValidationError,一次性包含所有错误

四、模型的核心方法

方法作用
model_validate(obj)验证任意对象(dict / 模型实例 / ORM 对象)
model_validate_json(str)直接验证 JSON 字符串(比先解析再验证更快)
model_validate_strings(dict)从全字符串 dict 验证(如表单数据)
model_construct()跳过验证直接建模型(性能场景)
model_dump() / model_dump_json()序列化为 dict / JSON 字符串
model_copy()复制模型(支持 deep=True)
model_json_schema()生成 JSON Schema,可与其他工具对接
model_rebuild()重建 schema(前向引用/递归模型时用)
model_fields / model_fields_set字段定义 / 本次显式传入的字段集合

五、字段定制:Field()

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(min_length=1, max_length=50, description="商品名")
    price: float = Field(gt=0, le=10000)          # 范围约束
    tags: list[str] = Field(default_factory=list) # 可变默认值必须用 factory
    alias: str = Field(alias="SKU")               # 输入别名

常用约束:gt/ge/lt/le、min_length/max_length、pattern、default_factory、alias、exclude、frozen 等。

六、自定义验证器(Validators)

1. 字段级验证器 — 四种模式

from pydantic import BaseModel, field_validator

class Model(BaseModel):
    number: int

    @field_validator('number', mode='after')   # after 是默认模式
    @classmethod
    def is_even(cls, value: int) -> int:
        if value % 2 == 1:
            raise ValueError(f'{value} is not an even number')
        return value                            # 必须返回验证后的值
模式时机
beforePydantic 内部解析之前,处理原始输入(可能是任意类型)
after内部验证之后,类型安全,最常用
plain立即终止验证,不经过 Pydantic 内部校验
wrap最灵活:包住内部验证,可 try/except、截断、提前返回

也可以用 Annotated 模式定义可复用的验证器:

from typing import Annotated
from pydantic import AfterValidator

def is_even(value: int) -> int:
    if value % 2 == 1:
        raise ValueError(f'{value} is not an even number')
    return value

EvenNumber = Annotated[int, AfterValidator(is_even)]

class Model(BaseModel):
    my_number: EvenNumber           # 可复用到任意模型
    evens: list[EvenNumber]         # 甚至作用于列表元素

2. 模型级验证器(跨字段校验)

from typing_extensions import Self
from pydantic import BaseModel, model_validator

class UserModel(BaseModel):
    username: str
    password: str
    password_repeat: str

    @model_validator(mode='after')
    def check_passwords_match(self) -> Self:
        if self.password != self.password_repeat:
            raise ValueError('Passwords do not match')
        return self

3. 验证上下文 ValidationInfo

验证器可接收 info: ValidationInfo,访问已验证的兄弟字段(info.data)和调用方传入的上下文(info.context)——常用于”某字段是否依赖另一字段""根据环境决定规则”等场景。

七、配置:ConfigDict

from pydantic import BaseModel, ConfigDict

class Model(BaseModel):
    model_config = ConfigDict(
        extra='forbid',              # 'ignore'忽略 | 'forbid'禁止 | 'allow'存到__pydantic_extra__
        strict=True,                 # 严格模式:不做类型转换
        frozen=True,                 # 不可变模型
        str_max_length=10,
        from_attributes=True,        # 允许从任意对象读取属性(ORM 集成)
        validate_assignment=True,    # 赋值时也校验
    )

严格模式(strict):默认宽松模式会尽力转换类型并可能丢失信息(如 3.000→3);开启 strict 后类型必须完全匹配。

八、嵌套模型与泛型模型

class Foo(BaseModel):
    count: int

class Spam(BaseModel):
    foo: Foo                          # 嵌套:dict 自动转成 Foo 实例
    bars: list[Foo]                   # list 中的每个 dict 元素也会被自动转成 Foo

泛型模型——复用通用结构(如统一响应包装):

from typing import Generic, TypeVar
from pydantic import BaseModel

T = TypeVar('T')   # 类型变量,用于声明泛型参数

class Response(BaseModel, Generic[T]):
    data: T        # 泛型序列化:实例化时指定具体类型来决定 data 的类型

# 用具体类型参数化泛型模型
print(Response[int](data=1))            # data=1(int 类型)
print(Response[list[str]](data=['a']))  # data=['a'](list[str] 类型)

泛型模型的核心价值:同一套响应包装结构可复用到任意数据类型,同时在实例化时获得完整的类型检查与校验。

九、错误处理

from pydantic import BaseModel, ValidationError

class Model(BaseModel):
    list_of_ints: list[int]
    a_float: float

try:
    Model(**{'list_of_ints': ['1', 2, 'bad'], 'a_float': 'not a float'})
except ValidationError as e:
    print(e.errors())
# 一次抛出所有错误,每条含:type、loc、msg、input、url
# 例如 [{'type': 'int_parsing', 'loc': ('list_of_ints', 2),
#        'msg': 'Input should be a valid integer...', 'input': 'bad'}]

注意:一次验证会收集所有字段错误(list_of_ints 索引 2 的 'bad' 和 a_float 的 'not a float'),而不是在第一个错误处就停止。

十、其他重要能力

  • Pydantic Settings:pydantic-settings 支持从环境变量/.env 文件读取配置校验
  • TypeAdapter:对单个类型(非模型)做验证/序列化,如 TypeAdapter(list[int]).validate_json(...)
  • Dataclasses / TypedDict:原生 dataclass 和 TypedDict 也能获得验证能力
  • JSON Schema 生成:model_json_schema() 一键对接 OpenAPI 等工具
  • ORM 集成:from_attributes=True 后可直接 model_validate() SQLAlchemy 对象

十一、v2 与 v1 的主要差异

v1v2说明
parse_obj() / parse_raw()model_validate() / model_validate_json()v1 的解析方法在 v2 中被 model_validate 系列方法取代
.dict() / .json()model_dump() / model_dump_json()v2 序列化接口统一使用 model_dump 系列
@validator / @root_validator@field_validator / @model_validator含 before/after/wrap 模式
Config 内部类model_config = ConfigDict(...)配置改为类属性 model_config 声明
update_forward_refs()model_rebuild()前向引用/递归模型的 schema 重建
纯 Python 实现Rust 核心(pydantic-core)性能提升 5-50 倍
__fields__model_fields字段元数据访问属性改名

十二、在一个 FastAPI + vLLM 网关中的实际应用

用真实项目(llm-service——一个 FastAPI + vLLM 的 LLM 业务网关)看 Pydantic 的落地方式。Pydantic 用在两处:请求/响应契约(schemas.py)和配置管理(pydantic-settings)。

1. 请求/响应契约 — schemas.py

from pydantic import BaseModel, Field
from typing import Literal

class Message(BaseModel):                        # 单条对话消息
    role: Literal["system", "user", "assistant"] # 枚举约束:非法 role 直接 422
    content: str

class ChatRequest(BaseModel):                    # 请求体
    messages: list[Message] = Field(..., min_length=1)   # 嵌套模型 + 非空校验
    temperature: float = Field(default=0.7, ge=0.0, le=2.0)  # 数值范围约束
    top_p: float = Field(default=1.0, ge=0.0, le=1.0)
    max_tokens: int = Field(default=2048, ge=1, le=4096)     # 防烧钱上限
    stream: bool = False
    user_id: str = ""

class Usage(BaseModel):                          # 嵌套响应对象
    prompt_tokens: int
    completion_tokens: int
    total_tokens: int

class ChatResponse(BaseModel):                   # 响应体
    id: str
    reply: str
    usage: Usage
    latency_ms: int
    from_cache: bool = False

用到的特性:

  • Field(..., min_length=1) 强制 messages 非空
  • ge/le 数值范围约束(temperature 0~2、max_tokens 上限 4096)
  • Literal 类型 → role 只能是 system/user/assistant,非法值自动 422
  • 嵌套模型 list[Message]:前端传 dict 数组,Pydantic 自动转成 Message 实例
  • 默认值:stream=False、from_cache=False 等

2. 在路由中如何被使用 — chat.py

@router.post("/v1/chat/completions")
async def chat(req: ChatRequest, request: Request, tenant: str = Depends(authenticate)):
    ...
  • FastAPI 自动注入:req: ChatRequest 作参数 → 请求体自动解析、校验,非法输入返回 422(含 Pydantic 错误详情),无需手写任何 if/else
  • 读字段:req.messages、req.temperature、req.stream、req.max_tokens
  • 模型转 dict:{"role": m.role, "content": m.content} for m in req.messages(直接属性访问)
  • 构造响应模型:缓存命中时 ChatResponse(id=f"c-{trace_id}", reply=cached, usage=Usage(prompt_tokens=0, ...));正常路径 usage=Usage(**usage) —— 用 ** 解包 dict 构造嵌套模型
  • FastAPI 返回 ChatResponse 时自动序列化为 JSON 响应

3. 配置管理 — config.py(pydantic-settings)

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_prefix="LLM_")

    vllm_base_url: str = "http://vllm:8000/v1"
    max_concurrency: int = 10
    api_keys: str = "sk-test-1111:tenant-a,sk-demo-2222:tenant-b"
    tenant_concurrency: dict = {"tenant-a": 5, "tenant-b": 5}
    cache_enabled: bool = True

settings = Settings()   # 模块级单例

用到的特性:

  • env_file=".env" + env_prefix="LLM_":环境变量 LLM_VLLM_BASE_URL 自动覆盖默认值,类型自动转换(str→int/bool/dict)
  • dict 类型字段 tenant_concurrency:环境变量传 JSON 字符串也能自动解析
  • 全局单例 settings,被 cache.py、llm_client.py、concurrency.py、auth.py 到处引用

4. 项目依赖版本

pydantic==2.9.2
pydantic-settings==2.6.1

5. 项目实践小结

用法位置效果
请求体校验 + 嵌套模型schemas.py + chat.py非法请求自动 422,零手写校验
字段约束Field(ge/le/min_length)数值范围、非空约束
响应模型schemas.py结构化返回 + FastAPI 自动序列化
配置管理config.py环境变量驱动,类型安全

需要特别强调的是:这个项目没有用到任何进阶特性——没有自定义验证器(field_validator/model_validator)、没有 strict 严格模式、没有主动调用 model_dump(序列化完全交由 FastAPI 自动完成)。项目的 Pydantic 用法停留在”基本款”:仅依赖 FastAPI 内置集成即可完成全部工作。这恰恰印证了一个关键结论——Pydantic 的价值在于渐进式引入:最简场景下只需声明类型注解 + Field 约束就能获得可靠的输入校验;当业务出现跨字段校验、自定义解析规则等复杂需求时,再按需引入验证器、严格模式等高级能力。不必一开始就铺满全部特性。

十三、生态与典型应用

  • FastAPI:请求体、响应体、查询参数全部基于 Pydantic,自动生成 OpenAPI 文档
  • LangChain / Pydantic AI:结构化输出、工具调用的参数校验
  • SQLModel:SQLAlchemy + Pydantic 结合,ORM 与验证一体
  • 配置管理:pydantic-settings 读环境变量并校验类型

参考资料(已联网核实的最新官方文档):

Sources

No external sources for this entry.

Related