Python——Pydantic库简单学习


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
    34
    from 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
    15
    from 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) # 大小限制

    # 自定义验证器:将首字母大写,并且其他都小写
    @field_validator('name')
    @classmethod
    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
    14
    from 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
    32
    from 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
    28
    from 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
    8
    from 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
    7
    from typing import Optional
    from pydantic import BaseModel

    class User(BaseModel):
    id: int
    name: str
    age: Optional[int]
  • 默认值 :可以在定义字段时直接设置默认值。例如:

    1
    2
    3
    4
    5
    6
    from pydantic import BaseModel

    class User(BaseModel):
    id: int
    name: str = "Jane Doe"
    age: int = 18
  • 允许多种数据类型 :通过类型提示允许字段接受多种数据类型。例如:

    1
    2
    3
    4
    5
    6
    from 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
    11
    from 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
    11
    from 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
    10
    from pydantic import BaseModel, validator

    class User(BaseModel):
    age: int

    @validator('age')
    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
    3
    from 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
    35
    class 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