Pydantic 整体说明
- Pydantic 是一个基于 Python 类型注解(Type Hints)的数据验证和设置管理工具
- Pydantic 库可利用 Python 类型提示对数据进行验证、解析、转换以及管理,确保数据的完整性、规范性和一致性
- Pydantic 库还方便数据在不同格式间的序列化与反序列化
Pydantic 安装
- 使用以下命令安装 Pydantic:
1
pip install pydantic
Pydantic 定义数据模型类并初始化对象
- 定义数据模型类并初始化类对象(自动验证类型)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34from pydantic import BaseModel, ValidationError
# 通过继承 `BaseModel` 来定义数据模型类,类中的字段使用类型注解来指定数据类型
class User(BaseModel):
id: int
name: str
age: int
is_active: bool = True
# 创建数据模型的实例时,Pydantic 会自动进行数据验证和解析
# * 如果数据符合模型定义,就可以正常创建实例
# * 如果数据无效,会抛出 `ValidationError` 异常
# 等价方式1:
data = {"id": 1, "name": "Alice", "age": 30} # 注意:缺少没有添加默认值的字段也会抛出异常
user = User(**data) # 等价于:User(id=1, name="Alice", age="30")
print(user) # 输出:d=1 name='Alice' age=30 is_active=True
print(user.name) # 输出:Alice
# 等价方式2(更清晰,建议使用这个):
# * model_validate(data) 支持更严谨的验证模式,如传参 strict=True 禁止参数自动转换等
# * model_validate(data) 明确告诉类型检查器:“这是一个待验证的外部数据”,从而避免了不必要的类型警告
user = User.model_validate(data)
print(user) # 输出:d=1 name='Alice' age=30 is_active=True
print(user.name) # 输出:Alice
invalid_data = {
"id": "invalid", # id应该是int类型
"age": "thirty" # age应该是int类型
}
try:
user = User(**invalid_data) # 错误,触发异常
except ValidationError as e:
print(e)
字段约束与自定义验证
- 使用
Field函数可以定义丰富的约束条件,使用@field_validator可以编写自定义校验逻辑1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from pydantic import BaseModel, Field, field_validator
class Product(BaseModel):
name: str = Field(..., min_length=1, max_length=50) # 长度限制
price: float = Field(..., gt=0, le=99999) # 大小限制
# 自定义验证器:将首字母大写,并且其他都小写
def capitalize_name(cls, v: str) -> str:
return v.capitalize() # 返回一个将首字母大写、其余所有字母小写的新字符串,中文则不变(第一个字符为中文,则默认为大写,字不变)
data = {"name": "beiJING", "price": 100}
product = Product.model_validate(data)
print(product) # 输出:name='Beijing' price=10.0
嵌套模型与复杂结构
- 支持在一个模型中嵌套另一个模型,处理复杂的 JSON 结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
class UserWithAddress(BaseModel):
id: int
name: str
address: Address # 嵌套模型也可以递归解析
data = {"id": 1, "name": "Alice", "address": {"street": "123 Main St", "city": "Springfield"}}
user = UserWithAddress.model_validate(data) # 递归解析结果
print(user) # id=1 name='Alice' address=Address(street='123 Main St', city='Springfield')
序列化与数据导出
- 将模型实例转换回字典或 JSON 格式,支持灵活的字段过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32from pydantic import BaseModel, Field
class User(BaseModel):
id: int = Field(alias='user_id')
# id: int
# user_id: int = Field(alias="id")
name: str = None # 特殊说明:可以在这里定义为默认值 None,但是后续传参数必须是字符串,不能是 None
age: int
is_active: bool = True # 默认值 True
data = {"user_id": 1, "age": 30}
user = User.model_validate(data)
# 将 pydantic.BaseModel 对象转换为字典
user_dict = user.model_dump()
print(user_dict) # {'id': 1, 'name': None, 'age': 30, 'is_active': True}
# 高级过滤参数:
# exclude_none=True : 过滤掉值为 None 的字段(默认为 False)
# exclude_unset=True : 仅保留用户实际传入的字段,屏蔽默认值(默认为 False)
# exclude={'age'} : 临时排除指定字段
# include={'age'} : 仅保留指定字段
# by_alias=True : 保留别名(默认为 False)
print(user.model_dump()) # {'id': 1, 'name': None, 'age': 30, 'is_active': True}
print(user.model_dump(exclude_none=True)) # {'id': 1, 'age': 30, 'is_active': True}
print(user.model_dump(exclude_unset=True)) # {'id': 1, 'age': 30}
print(user.model_dump(exclude={'age'})) # {'id': 1, 'name': None, 'is_active': True}
print(user.model_dump(include={'age'})) # {'age': 30}
print(user.model_dump(by_alias=True)) # {'user_id': 1, 'name': None, 'age': 30, 'is_active': True}
data = {"user_id": 1, "age": 30, "is_active": True}
user = User.model_validate(data)
print(user.model_dump(exclude_defaults=True)) # {'id': 1, 'age': 30}
驼峰 和 下划线命名统一 & 全局配置
- 用途:在前后端分离项目中,常遇到前端使用驼峰命名(如
userName),后端使用下划线命名(如user_name)的痛点 - Pydantic v2 提供了优雅的解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
class User(BaseModel):
# 使用 ConfigDict 进行全局配置,重写 User 类对象的 model_config 属性
model_config = ConfigDict(
alias_generator=to_camel, # 自动生成驼峰别名,别名的高阶用法
populate_by_name=True, # 允许同时使用原字段名和别名传参
str_strip_whitespace=True, # 全局自动去除字符串首尾空格
)
# 定义核心字段
user_name: str
user_age: int
# 支持前端驼峰传参
user = User.model_validate({"userName": "张三", "userAge": 22})
# 序列化时输出驼峰格式
print(user.model_dump(by_alias=True)) # {'userName': '张三', 'userAge': 22}
# 支持下划线传参(因为:populate_by_name=True)
user = User.model_validate({"user_name": "张三", "user_age": 22})
# 序列化时输出驼峰格式
print(user.model_dump(by_alias=True)) # {'userName': '张三', 'userAge': 22}
# 动去除字符串首尾空格
user = User.model_validate({"userName": "张三 ", "userAge": 22})
print(user.model_dump(by_alias=True)) # {'userName': '张三', 'userAge': 22}
敏感数据隐藏
- 可以通过配置自动排除密码等敏感字段
1
2
3
4
5
6
7
8from pydantic import BaseModel, Field
class SecureUser(BaseModel):
username: str
password: str = Field(exclude=True) # 序列化时自动隐藏
user = SecureUser(username="admin", password="secret123")
print(user.model_dump()) # {'username': 'admin'},密码字段自动消失
字段定义相关的高级特性
可选字段 :使用
Optional类型来定义可选字段,例如:1
2
3
4
5
6
7from typing import Optional
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
age: Optional[int]默认值 :可以在定义字段时直接设置默认值。例如:
1
2
3
4
5
6from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "Jane Doe"
age: int = 18允许多种数据类型 :通过类型提示允许字段接受多种数据类型。例如:
1
2
3
4
5
6from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: Union[int, str]
name: str枚举类型 :Pydantic支持枚举类型,用于限制字段的值只能是预定义的一组值之一。例如:
1
2
3
4
5
6
7
8
9
10
11from enum import Enum
from pydantic import BaseModel
class Gender(str, Enum):
MALE = "male"
FEMALE = "female"
class User(BaseModel):
id: int
name: str
gender: Gender嵌套模型 :可以定义嵌套的模型来表示复杂的数据结构。例如:
1
2
3
4
5
6
7
8
9
10
11from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
id: int
name: str
address: Address自定义验证器 :使用
validator装饰器可以在数据被解析后进行额外的验证。例如:1
2
3
4
5
6
7
8
9
10from pydantic import BaseModel, validator
class User(BaseModel):
age: int
def check_age(cls, value):
if value < 0:
raise ValueError('Age must be a non - negative integer')
return value
附录:一些其他功能
其他函数
- 数据模型实例具有一些属性和方法,如
dict()返回模型字段和值的字典,json()返回 JSON 字符串表示,copy()创建模型的副本等
动态创建模型
- 使用
create_model方法可以动态创建模型(不常用):1
2
3from pydantic import create_model
DynamicModel = create_model('DynamicModel', foo=(str, ...), bar=123)
附录:VeRL 使用示例
OpenAI Function Calling 格式定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20{
"type": "function", # 固定值
"function": {
"name": "tool_name", # 工具名称
"description": "工具描述文本", # 工具描述
"parameters": {
"type": "object",
"properties": {
"param_name": {
"type": "string | integer | ...", # 参数类型
"description": "参数描述", # 可选
"enum": ["option1", "option2"] # 可选,枚举值
},
# ... 更多参数
},
"required": ["param1", "param2"] # 必填参数列表
},
"strict": False # 是否启用严格模式
}
}VeRL 中将上诉定义表述为嵌套多层的 Pydantic 数据模型(
import verl.tools.schemas)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class OpenAIFunctionPropertySchema(BaseModel):
"""The schema of a parameter in OpenAI format."""
# Union's type is list[str], e.g. ["integer", "number"] for int | float unions.
type: str | list[str]
description: str | None = None
# JSON Schema's ``enum`` accepts any JSON value, not just strings, so
# ``Literal[1, 2, 3]`` -> ``enum: [1, 2, 3]`` is a valid schema.
enum: list[Any] | None = None
class OpenAIFunctionParametersSchema(BaseModel):
"""The schema of parameters in OpenAI format."""
type: str
properties: dict[str, OpenAIFunctionPropertySchema]
# ``required`` can be omitted when no parameter is required.
required: list[str] = Field(default_factory=list)
class OpenAIFunctionSchema(BaseModel):
"""The schema of a function in OpenAI format."""
name: str
description: str
parameters: OpenAIFunctionParametersSchema = Field(
default_factory=lambda: OpenAIFunctionParametersSchema(type="object", properties={}, required=[])
)
strict: bool = False
class OpenAIFunctionToolSchema(BaseModel):
"""The schema of a tool in OpenAI format."""
type: str
function: OpenAIFunctionSchema