概念
models
1. 核心目的与 BaseModel
目的: Pydantic 模型的主要目的是定义清晰、结构化、类型安全的数据模式。它们用于:
- 数据解析 (Parsing): 将原始数据(如字典、JSON)转换为类型化的 Python 对象。
- 数据验证 (Validation): 确保输入数据符合定义的类型和约束。
- 数据序列化 (Serialization): 将 Python 对象转换回字典或 JSON 等格式。
- 编辑器支持: 提供出色的自动补全、类型检查和重构能力。
BaseModel: 所有 Pydantic 模型都应继承自pydantic.BaseModel。这是所有魔法发生的基础。from pydantic import BaseModel class User(BaseModel): id: int name: str signup_ts: datetime | None = None # Optional field with default None friends: list[int] = [] # Field with a default value
2. 定义字段 (Fields)
- 类型提示: 模型字段使用标准的 Python 类型提示来定义。Pydantic 会利用这些类型提示进行验证和转换。
- 支持所有标准 Python 类型 (
int,str,float,bool,list,dict,tuple,set,datetime,UUID, etc.)。 - 支持
typing模块中的类型 (Optional,Union,Literal,List,Dict,Any, etc.)。 Python 3.10+ 可以使用|代替Union。
- 支持所有标准 Python 类型 (
- 必需字段 (Required): 仅提供类型提示,没有默认值。实例化时必须提供。
id: int # Required - 可选字段 (Optional): 使用
Optional[<type>]或<type> | None。默认值为None。signup_ts: Optional[datetime] # Default is None # OR (Python 3.10+) signup_ts: datetime | None # Default is None - 带默认值的字段: 直接在字段定义中赋值,或使用
Field(default=...)。friends: list[int] = [] # Default is an empty list # OR using Field from pydantic import Field friends: list[int] = Field(default_factory=list) # Use default_factory for mutable defaults age: int = Field(default=30)- 注意: 对于可变类型(如
list,dict),推荐使用default_factory以避免所有实例共享同一个默认对象。
- 注意: 对于可变类型(如
3. 创建模型实例 (Validation & Parsing) - V2 API
model_validate()(首选): 从字典或其他兼容对象创建模型实例,并执行验证。这是 V2 中推荐的主要方式。external_data = {'id': 123, 'name': 'Alice', 'friends': [1, 2]} user = User.model_validate(external_data) print(user) #> id=123 name='Alice' signup_ts=None friends=[1, 2]model_validate_json(): 直接从 JSON 字符串创建模型实例,并执行验证。json_data = '{"id": 123, "name": "Alice", "friends": [1, 2]}' user = User.model_validate_json(json_data) print(user) #> id=123 name='Alice' signup_ts=None friends=[1, 2]__init__(**kwargs): 也可以使用标准的User(**external_data)初始化,它内部会调用验证逻辑。但model_validate更明确地表达了“验证并创建”的意图。ValidationError: 如果输入数据不符合模型定义(类型错误、缺少必需字段、违反约束等),Pydantic 会抛出pydantic.ValidationError异常,其中包含详细的错误信息。
4. 访问模型数据
- 像访问普通 Python 对象的属性一样访问模型字段:
user.id,user.name。 - IDE 会提供很好的自动补全。
5. 导出模型数据 (Serialization) - V2 API
model_dump(): 将模型实例转换回 Python 字典。替代 V1 的.dict()。user_dict = user.model_dump() print(user_dict) #> {'id': 123, 'name': 'Alice', 'signup_ts': None, 'friends': [1, 2]}- 常用参数:
mode='python' | 'json': 控制输出类型('json'会将datetime转为 ISO 格式字符串等)。include: 指定要包含的字段集合。exclude: 指定要排除的字段集合。exclude_unset=True: 只导出显式设置或有默认值的字段(不导出值为None且未设置的Optional字段)。exclude_defaults=True: 不导出值为其默认值的字段。exclude_none=True: 不导出值为None的字段。by_alias=True: 使用字段别名(如果定义了)作为字典的键。
- 常用参数:
model_dump_json(): 将模型实例直接转换为 JSON 字符串。替代 V1 的.json()。user_json = user.model_dump_json(indent=2) print(user_json) # Output: # { # "id": 123, # "name": "Alice", # "signup_ts": null, # "friends": [ # 1, # 2 # ] # }- 接受与
model_dump()类似的参数(如include,exclude,by_alias)以及 JSON 相关的参数(如indent)。
- 接受与
6. 嵌套模型 (Nested Models)
- 可以将一个
BaseModel类型用作另一个模型的字段类型。Pydantic 会自动处理嵌套结构的解析、验证和序列化。class Address(BaseModel): street: str city: str zip_code: str class UserWithAddress(BaseModel): id: int name: str address: Address # Nested model user_data = { "id": 1, "name": "Bob", "address": { "street": "123 Main St", "city": "Anytown", "zip_code": "12345" } } user = UserWithAddress.model_validate(user_data) print(user.address.city) #> Anytown print(user.model_dump()) #> {'id': 1, 'name': 'Bob', 'address': {'street': '123 Main St', 'city': 'Anytown', 'zip_code': '12345'}}
7. 模型配置 (model_config) - V2 方式
- 通过在模型内部定义一个名为
model_config的类变量 (通常是pydantic.ConfigDict或普通字典) 来自定义模型的行为。替代 V1 的class Config:。from pydantic import BaseModel, ConfigDict class MyModel(BaseModel): model_config = ConfigDict( frozen=True, # 使模型实例不可变 extra='ignore', # 忽略验证期间模型未定义的额外字段 # extra='forbid', # 禁止额外的字段 (默认) # extra='allow', # 允许额外的字段但不进行验证 populate_by_name=True, # 允许通过字段名和别名初始化/赋值 (V1 默认行为) validate_assignment=True, # 在给字段赋值时进行验证 str_strip_whitespace=True, # 自动去除字符串字段的前后空白 # ... 更多配置项 ) id: int name: str
8. 字段定制 (Field)
pydantic.Field函数允许更精细地控制字段的行为,如添加默认值、别名、验证约束、描述等。
from pydantic import BaseModel, Field
from typing import Annotated
class AdvancedUser(BaseModel):
id: int = Field(..., gt=0) # Required ( Ellipsis ... means required), must be > 0
username: str = Field(min_length=3, max_length=50)
# Use alias for data sources with different naming conventions
real_name: str = Field(alias="fullName")
age: int | None = Field(default=None, le=120) # Optional, default None, <= 120
tags: list[str] = Field(default_factory=list)
description: str = Field(description="A brief description of the user.")
# V2.7+ 推荐使用 Annotated 进行约束
class AnnotatedUser(BaseModel):
id: Annotated[int, Field(gt=0)]
username: Annotated[str, Field(min_length=3, max_length=50)]
real_name: Annotated[str, Field(alias="fullName")]
# ...
当 Field 函数的第一个参数是 Python 内置的 … (Ellipsis 字面量) 时,它对 Pydantic 有一个特殊含义:这个字段是必需的 (required)。
- 别名 (Alias): 使用
Field(alias='...')来指定字段在输入/输出数据(如 JSON)中的名称。这对于处理非 Pythonic 命名(如驼峰式camelCase)或关键字冲突很有用。validation_alias和serialization_alias可以分别控制验证和序列化时的别名。
typing.Annotated 是 Python 3.9 引入的一个标准库功能 (在 PEP 593 中定义)。它的主要目的是提供一种标准化的方式,将上下文特定的元数据 (metadata) 附加到已有的类型提示上,而不改变类型本身。
语法是:Annotated[<类型>, <元数据1>, <元数据2>, ...]
为什么 Pydantic V2.7+ 推荐使用 Annotated?
Pydantic 团队发现,使用 = Field(…) 的语法虽然可行,但有时会引起一些概念上的混淆:
语义模糊: Field(…) 看起来像是在赋默认值,但 … 恰恰表示没有默认值(必需)。
关注点分离: 类型提示 (int) 和 Pydantic 特定的配置 (Field(…)) 混合在了一起。
Annotated 提供了一种更清晰、更符合 Python 设计哲学的方式来解决这个问题:
id: Annotated[int, Field(gt=0)]:int: 清晰地表明 id 的主要 Python 类型是整数。
Field(gt=0): 作为 Annotated 的第二个参数,它被明确地标记为附加到 int 类型上的元数据。Pydantic 知道查找 Annotated 中的 Field 对象来获取约束、别名、描述等信息。 使用 Annotated 的好处:
更清晰: 明确区分了字段的 Python 类型和附加的 Pydantic 配置/约束。int 就是类型,Field(gt=0) 是应用于该类型此字段的 Pydantic 元数据。
更符合标准: 使用的是 Python 标准库 (typing.Annotated) 的机制。
更好的可组合性: Annotated 可以包含多个元数据项,例如
Annotated[int, Field(gt=0, description="User ID"), SomeOtherMetadata()],这比将所有东西塞进 Field() 可能更清晰。处理必需字段更自然: 对于必需字段,你不再需要使用 … 这个有点奇怪的语法。如果字段是必需的,只需在 Annotated 中包含 Field 来添加约束即可,Pydantic 默认知道没有提供默认值的字段是必需的。
# Required field with constraint using Annotated
id: Annotated[int, Field(gt=0)]
Pydantic 模型是定义数据结构、执行验证和控制数据转换的核心工具。通过继承 BaseModel、使用类型提示定义字段,并结合 model_validate/model_dump 等 V2 API 以及可选的 model_config 和 Field 定制,可以构建强大、可靠且易于维护的数据处理逻辑。理解这些基本概念是有效使用 Pydantic 的关键。
Field
1. Field 的作用
- 核心目的: 当你需要在模型字段上添加超出基本类型提示的信息时,就需要使用
pydantic.Field。这些额外信息包括:- 默认值 (Defaults)
- 字段别名 (Aliases)
- 验证约束 (Validation Constraints)
- 序列化定制 (Serialization Customization)
- 元数据(描述、示例等)(Metadata)
- 不仅仅是类型: 类型提示 (
int,str,list[str]) 定义了字段的基本类型,而Field则提供了对该字段行为和属性的精细控制。
2. 如何使用 Field - 推荐方式: typing.Annotated
- Pydantic V2.7+ 推荐: 使用 Python 标准库
typing.Annotated是最清晰、最符合 Python 风格的方式。from typing import Annotated from pydantic import BaseModel, Field class Item(BaseModel): # 类型是 int, 附加了 Field 的元数据 (约束: > 0) item_id: Annotated[int, Field(gt=0)] # 类型是 str | None (可选), 附加了 Field 的元数据 (默认值, 别名, 描述) name: Annotated[str | None, Field(default=None, alias="itemName", description="Name of the item")] # 类型是 list[str], 附加了 Field 的元数据 (默认工厂, 约束) tags: Annotated[list[str], Field(default_factory=list, min_length=1)] - 优点:
- 清晰分离: 明确区分字段的 Python 类型 (
int,str | None) 和附加的 Pydantic 元数据 (Field(...))。 - 标准化: 使用 Python 内置机制。
- 可组合性:
Annotated可以包含多种元数据。
- 清晰分离: 明确区分字段的 Python 类型 (
3. 如何使用 Field - 传统方式: 默认值赋值
- 仍然有效,但不推荐用于新代码:
from pydantic import BaseModel, Field class OldStyleItem(BaseModel): # 必需字段 (用 ...), 带约束 item_id: int = Field(..., gt=0) # '...' means required when using Field assignment # 可选字段,带默认值、别名、描述 name: str | None = Field(default=None, alias="itemName", description="Name of the item") # 带默认工厂和约束 tags: list[str] = Field(default_factory=list, min_length=1) #- 它告诉 Pydantic:如果创建模型实例时**没有**为 tags 提供值,就调用 list() 这个函数来**动态生成**一个默认值。- list() 被调用时会返回一个新的、空的列表 ([])。因为提供了生成默认值的方法,这个字段现在变成了**可选的**。 - 缺点:
- 语义模糊:
= Field(...)看起来像赋值,但...意味着必需。 - 混合关注点:类型和 Pydantic 配置写在一起。
- 语义模糊:
4. Field 的常用参数 (元数据和约束)
以下是 Field 函数中一些最常用的参数:
default:- 为字段设置一个静态的默认值。
- 如果字段是可选的 (
Optional或| None),省略default则默认为None。 - 示例:
name: Annotated[str, Field(default="Unnamed")]
default_factory:- 指定一个无参数的可调用对象 (函数、lambda、类),用于生成字段的默认值。
- 强烈推荐用于可变类型 (如
list,dict,set),以确保每个模型实例获得独立的默认对象。 - 示例:
items: Annotated[list[int], Field(default_factory=list)]错误使用方式如tags: Annotated[list[str], Field(default=[])] # 注意:这里用 default会导致多个示例共享同一个对象的内存地址
alias:- 指定字段在输入验证 (解析) 和输出序列化时的名称。
- 常用于处理外部数据源 (如 JSON API) 使用不同命名规范 (如
camelCase) 的情况。 - 示例:
user_id: Annotated[int, Field(alias="userId")] - 注意: 需要在
model_config中设置populate_by_name=True才能同时通过别名和字段名进行初始化。
validation_alias:- 仅在输入验证 (解析) 时使用的别名。优先级高于
alias。 - 示例:
internal_name: Annotated[str, Field(validation_alias="externalInputName")]
- 仅在输入验证 (解析) 时使用的别名。优先级高于
serialization_alias:- 仅在输出序列化 (
model_dump,model_dump_json) 时使用的别名。优先级高于alias。 - 示例:
db_field: Annotated[str, Field(serialization_alias="apiOutputField")]
- 仅在输出序列化 (
- 验证约束 (Validation Constraints):
- 数字类型 (
int,float,Decimal):gt(大于),lt(小于)ge(大于等于 / min),le(小于等于 / max)multiple_of(必须是…的倍数)- 示例:
price: Annotated[float, Field(gt=0, le=1000)]
- 字符串类型 (
str):min_length,max_lengthpattern(正则表达式)- 示例:
zip_code: Annotated[str, Field(pattern=r"^\d{5}(-\d{4})?$")]
- 集合类型 (
list,set,tuple):min_length,max_length(在 V2 中统一使用 length,而非 V1 的 items)- 示例:
ingredients: Annotated[list[str], Field(min_length=1, max_length=10)]
- 数字类型 (
- 元数据 (Metadata):
title: 字段的简短、人类可读的标题。description: 字段的详细描述。examples: 一个包含示例值的列表。- 这些元数据主要用于自动生成文档 (如 OpenAPI Schema)。
- 示例:
user_agent: Annotated[str | None, Field(description="Client user agent string", examples=["Mozilla/5.0", "MyApp/1.2"])]
- 行为控制 (Behavior Control):
repr=True|False: 控制此字段是否包含在模型实例的repr()输出中 (默认为True)。init_var=True|False: 如果为True(默认为False),该字段仅在模型初始化 (__init__) 时可用,之后不会存储在模型实例上,也不会参与验证或序列化。kw_only=True|False: 如果为True(默认为False),该字段在模型初始化时必须作为关键字参数传递。frozen=True|False: 如果为True(默认为False),使该特定字段在模型实例创建后不可修改。这不同于model_config中的frozen=True(使整个模型不可变)。
Field 函数(推荐通过 Annotated 使用)是 Pydantic 中定制字段行为的关键工具。它允许你超越基本的类型定义,添加默认值、处理命名差异(别名)、施加复杂的验证规则、丰富文档信息,并微调字段在初始化和表示时的行为。熟练掌握 Field 的用法对于构建健壮、灵活的数据模型至关重要。
JSON Schema
1. 什么是 JSON Schema?
- 定义: JSON Schema 是一个用于描述和验证 JSON 数据结构的标准化词汇(通常本身也用 JSON 编写)。
- 作用: 它定义了 JSON 文档应该具有的属性、类型、格式、约束等。你可以把它看作是 JSON 数据的“类型注解”或“接口定义”。
- 标准: 这是一个独立于任何特定编程语言或库的开放标准。
2. Pydantic 与 JSON Schema 的关系
- 核心能力: Pydantic 的一个强大功能是能够从你的
BaseModel定义中自动生成对应的 JSON Schema。 - 单一事实来源 (Single Source of Truth): 你的 Pydantic 模型成为了数据结构、验证规则和其 JSON Schema 表示的唯一来源。修改模型定义会自动更新生成的 Schema。
- 目的: 生成的 JSON Schema 可以用于多种目的,最常见的是驱动 API 文档和在其他系统/语言中验证数据。
3. 如何生成 JSON Schema
- 类方法:
model_json_schema(): 每个 PydanticBaseModel类都有一个类方法model_json_schema(),用于生成该模型的 JSON Schema。from pydantic import BaseModel, Field from typing import List, Optional class User(BaseModel): id: int = Field(..., title='User ID', gt=0) name: str = Field(default='John Doe', title='Full Name', min_length=2) friends: List[int] = Field(default_factory=list) age: Optional[int] = Field(None, ge=0, le=120) # Using Optional directly # Generate the JSON Schema for the User model user_schema = User.model_json_schema() import json print(json.dumps(user_schema, indent=2)) - 输出: 输出是一个 Python 字典,其结构遵循 JSON Schema 规范。例如,上面的代码可能生成(简化示例):
{ "title": "User", "type": "object", "properties": { "id": { "title": "User ID", "type": "integer", "exclusiveMinimum": 0 // Corresponds to gt=0 }, "name": { "title": "Full Name", "minLength": 2, "default": "John Doe", "type": "string" }, "friends": { "title": "Friends", "default": [], "type": "array", "items": { "type": "integer" } }, "age": { "title": "Age", "maximum": 120, "minimum": 0, // Corresponds to ge=0 "anyOf": [ // Represents Optional[int] or int | None { "type": "integer" }, { "type": "null" } ], "default": null } }, "required": [ "id" // Only id is required because others have defaults ] }
4. 生成 JSON Schema 的主要用途
- API 文档 (OpenAPI / Swagger): 这是最常见的用途。像 FastAPI 这样的框架会使用 Pydantic 模型的
model_json_schema()来自动生成 OpenAPI 规范。这个规范随后可以被 Swagger UI 或 ReDoc 等工具用来展示交互式的 API 文档。用户和客户端开发者可以清楚地看到 API 的请求/响应结构和约束。 - 跨系统/语言的数据验证: 你可以在一个使用 Pydantic 的 Python 服务中定义数据模型并生成其 JSON Schema,然后在另一个系统(可能用不同语言编写,如 JavaScript、Java)中使用这个 Schema 来验证传入或传出的 JSON 数据是否符合预期格式。
- 配置验证: 可以为配置文件(如 JSON 格式)定义 Pydantic 模型,并导出其 JSON Schema,用于验证配置文件的正确性。
- 用户界面生成: 基于 JSON Schema 可以动态生成数据输入表单。
- 代码生成: 理论上可以基于 JSON Schema 生成其他语言的数据类或客户端代码。
5. 自定义 JSON Schema 输出
虽然 Pydantic 会根据类型提示和 Field 的约束自动生成大部分 Schema,但有时你需要添加额外的、非验证性的元数据(如示例)或覆盖某些 Schema 属性。
Field(..., json_schema_extra=...):- 可以在
Field中使用json_schema_extra参数传入一个字典或一个可调用对象 (callable)。 - 如果传入字典,其键值对会被合并到该字段生成的 Schema 中。
- 如果传入 callable,它会接收生成的字段 Schema 字典作为参数,并允许你原地修改它。
from typing import Annotated class Product(BaseModel): product_id: Annotated[str, Field( json_schema_extra={'examples': ['prod-123', 'prod-abc']}, pattern='^prod-' # Validation constraint )] price: Annotated[float, Field( gt=0, json_schema_extra=lambda schema: schema.update(format='currency') # Add custom format )]- 可以在
model_config中的json_schema_extra:- 可以在模型的
model_config中定义json_schema_extra,其作用于整个模型的顶层 Schema 对象。同样可以接受字典或 callable。
from pydantic import BaseModel, ConfigDict class ConfiguredModel(BaseModel): model_config = ConfigDict( json_schema_extra={'example': {'name': 'Example Name'}} ) name: str- 可以在模型的
6. model_json_schema() 的参数
by_alias: bool = True: 控制 Schema 中的属性名 (properties的键) 使用字段的别名 (如果定义了) 还是 Python 字段名。默认为True,这通常是在 API 场景下期望的行为(API 接口使用别名)。ref_template: str = '#/$defs/{model}': 控制生成的 Schema 中内部引用 ($ref) 的格式模板。schema_generator: type[GenerateJsonSchema] = ...: 允许传入一个自定义的 Schema 生成器类,用于更深层次的定制(高级用法)。mode: Literal['validation', 'serialization'] = 'validation': 指定是为验证目的还是序列化目的生成 Schema。大多数情况下两者相同,但在使用如validation_alias和serialization_alias时可能有细微差别。
7. JSON Schema vs Pydantic Core Schema
- Pydantic V2 内部: Pydantic V2 使用一个更强大、基于 Rust 的内部表示,称为 “Core Schema”,来执行实际的验证和序列化逻辑。这个 Core Schema 比 JSON Schema 更具表达力。
- 生成关系:
model_json_schema()的过程实际上是:BaseModel-> Pydantic Core Schema -> JSON Schema。 - JSON Schema 的角色: JSON Schema 是 Pydantic 对外提供的一种标准化表示,用于与其他系统集成和文档生成,而不是 Pydantic 内部直接用于验证的核心机制。
- 可以通过
YourModel.__pydantic_core_schema__或实现__get_pydantic_core_schema__来访问或定制 Core Schema(高级)。
总结:
Pydantic 模型不仅是 Python 中的数据容器和验证器,还是生成标准 JSON Schema 的强大工具。通过 model_json_schema() 方法,可以轻松地将模型定义转换为符合规范的 Schema,极大地促进了 API 文档自动化、跨系统数据验证和互操作性。理解如何生成和定制 JSON Schema 对于充分利用 Pydantic 在 API 开发和数据集成中的优势至关重要。
