目录
- 前言
- [一、Pydantic 核心概述](#一、Pydantic 核心概述)
- [二、BaseModel 核心原理](#二、BaseModel 核心原理)
-
- 底层工作原理-元类驱动
- [`init`: 参数校验、数据解析、类型转换](#
__init__: 参数校验、数据解析、类型转换) - 自动序列化/反序列化
- 核心设计思想
- 三、BaseModel实战使用
- [四、BaseModel 核心方法总结](#四、BaseModel 核心方法总结)
前言
- 在 Python 开发中,数据校验 和数据解析是绕不开的核心环节 ------ 无论是接口入参校验、配置文件解析,还是业务数据结构化,手动编写校验逻辑不仅繁琐易错,还会大幅降低开发效率。
- Pydantic 正是解决这一问题的神器,它凭借类型提示实现自动化数据校验与转换,是 FastAPI、LangChain 等主流框架的底层依赖。
一、Pydantic 核心概述
Pydantic 是一个基于 Python 类型提示 的数据验证 和设置管理库,核心特性:
- 自动数据校验:基于类型注解校验数据合法性,抛出清晰的异常信息;
- 自动数据转换:兼容类型自动转换(如字符串"18"转整数18);
- 结构化数据:将字典、JSON 等松散数据转为强类型对象;
Pydantic 的所有能力,都基于 BaseModel 基类 实现 ------ 它是所有自定义数据模型的父类,也是 Pydantic 的核心。
二、BaseModel 核心原理
BaseModel 是 Pydantic 提供的核心基类,自定义模型继承它后,会自动获得数据校验、解析、类型转换、序列化等能力
底层工作原理-元类驱动
我们先简单过下python元类的概念
- python 中的
__new__魔法方法用于创建对象实例,__init__用于初始化对象属性,__new__的执行在__init__之前 - 普通类的
__new__方法用于创建实例对象,而元类的__new__方法用于创建类对象。 - 可以指定元类,如果不指定,普通类的默认元类是
type类 - 如果自定义类有父类,且自定义类没有指定元类,python解释器会向上查找父类的元类,用于创建自定义类对象
BaseModel 基于 Python 元类(ModelMetaclass) 实现:
- 当你定义一个类继承 BaseModel 时,元类会自动扫描类中所有带类型注解的字段;
- 元类会收集字段的类型、约束、默认值、校验规则,生成带参数解析、检验的构造函数;
- 元类会生成序列化/反序列化的函数
__init__: 参数校验、数据解析、类型转换
参数校验、数据解析、类型转换 在 元类自动生成的 __init__ 方法中完成
attrs是 Python 解释器自动解析完「类的代码」后,提前打包好的普通字典,用于传入__new__方法,包括类属性、__annotation__、函数、特殊属性(__name__)等内容- 需要说明的是,所有的
attrs中的属性都会最终赋值到创建的类对象的__dict__属性字典中,类.__dict__是Python 中所有类的属性 / 方法存储仓库;super().__new__:搬运工 → 把草稿纸(attrs)的内容,搬进仓库(类.__dict__)
python
class MyBaseModelMeta(type):
def __new__(cls, name, bases, attrs):
# 🔥 关键:直接打印 attrs,看它的真实结构!
print(f"===== 类 {name} 的 attrs 字典 =====\n", attrs, "\n")
# 原来的逻辑不变
fields = {}
for key, type_ in attrs.get('__annotations__', {}).items():
fields[key] = type_
def __init__(self, **kwargs):
for field, expected_type in fields.items():
if field not in kwargs:
raise TypeError(f"缺少字段: {field}")
value = kwargs[field]
# 字符串转数字/布尔(简化版,真实Pydantic兼容更多类型)
if expected_type == int and isinstance(raw_value, str):
value = int(raw_value)
elif expected_type == float and isinstance(raw_value, str):
value = float(raw_value)
elif expected_type == bool and isinstance(raw_value, str):
value = raw_value.lower() == "true"
else:
value = raw_value
if not isinstance(value, expected_type):
raise TypeError(f"{field} 必须是 {expected_type} 类型")
setattr(self, field, value)
attrs['__init__'] = __init__
attrs['__fields__'] = fields # 这个元数据字典很重要
return super().__new__(cls, name, bases, attrs)
class MyBaseModel(metaclass=MyBaseModelMeta):
pass
```
自动序列化/反序列化
BaseModel 内置了模型实例 ↔ 字典 / JSON 的双向转换逻辑:
- 序列化:模型对象 → 字典 / JSON 字符串(一行代码完成)
- 反序列化:字典 / JSON → 模型对象(自动校验)
自动序列化:model_dump / model_dump_json,也是在元类的__new__中创建的两个功能,序列化完全依赖 __fields__ 元数据。
python
class MyBaseModelMeta(type):
def __new__(cls, name, bases, attrs):
# 🔥 关键:直接打印 attrs,看它的真实结构!
print(f"===== 类 {name} 的 attrs 字典 =====\n", attrs, "\n")
# 原来的逻辑不变
fields = {}
for key, type_ in attrs.get('__annotations__', {}).items():
fields[key] = type_
# def __init__(self, **kwargs):
def model_dump(self):
# 靠 __fields__ 遍历所有字段,无需手动写字段名
return {field: getattr(self, field) for field in self.__fields__}
# ==============================================
# 自动生成 model_dump_json:模型 → JSON字符串
# ==============================================
def model_dump_json(self):
return json.dumps(self.model_dump(), ensure_ascii=False)
# 把自动生成的方法绑定到子类
attrs['__init__'] = __init__
attrs['model_dump'] = model_dump
attrs['model_dump_json'] = model_dump_json
attrs['__fields__'] = fields # 这个元数据字典很重要
return super().__new__(cls, name, bases, attrs)
class MyBaseModel(metaclass=MyBaseModelMeta):
pass
自动反序列化,直接基于json字符串和字典,构建baseModel实例对象,当然,也可以直接通过构造方法直接解包构建实例对象
python
class MyBaseModelMeta(type):
def __new__(cls, name, bases, attrs):
# 前面逻辑不变
@classmethod
def model_validate(cls, data: dict):
"""字典 → 模型实例(反序列化核心)"""
# 直接调用类的 __init__ 完成校验+创建实例
return cls(**data)
@classmethod
def model_validate_json(cls, json_str: str):
"""JSON字符串 → 模型实例"""
data = json.loads(json_str)
return cls.model_validate(data)
# 把所有方法写入 attrs,最终进入类的 __dict__
attrs['model_validate'] = model_validate
attrs['model_validate_json'] = model_validate_json
return super().__new__(cls, name, bases, attrs)
核心设计思想
- 约定优于配置:用 Python 原生类型提示替代复杂的校验规则;
- 运行时保障:在代码运行时直接校验数据;
- 面向对象:将松散数据封装为对象,提升代码可读性和可维护性。
三、BaseModel实战使用
定义模型
继承 BaseModel,通过类型注解定义字段,即可快速创建数据模型。
python
from pydantic import BaseModel
# 定义用户模型
class User(BaseModel):
# 字段:类型注解(必填)
name: str
age: int
# 字段:类型 + 默认值(选填)
email: str | None = None # 可选字段,默认 None
# 1. 实例化模型(传入字典/关键字参数)
user1 = User(name="张三", age=18)
user2 = User({"name": "李四", "age": 20, "email": "lisi@example.com"})
# 2. 属性访问(强类型,无 KeyError)
print(user1.name) # 输出:张三
print(user2.age) # 输出:20
print(user2.email) # 输出:lisi@example.com
高级字段校验(Field 函数)
Field 函数用于对字段添加精细化校验规则(长度、范围、正则、描述等),是业务开发最常用的功能。
常用参数:
-
gt/ge:大于 / 大于等于;lt/le:小于 / 小于等于; -
min_length/max_length:字符串长度限制; -
regex:正则表达式校验; -
default:默认值; -
description:字段描述(生成文档用)。pythonfrom pydantic import BaseModel, Field class Product(BaseModel): # 商品名:长度 2-20 个字符 name: str = Field(min_length=2, max_length=20, description="商品名称") # 价格:大于 0,小于 10000 price: float = Field(gt=0, lt=10000, description="商品价格") # 手机号:正则校验 phone: str = Field(regex=r"^1[3-9]\d{9}$", description="手机号") # 合法数据 product = Product( name="笔记本电脑", price=5999.9, phone="13812345678" ) # 非法数据(价格负数、手机号错误)会直接抛出 ValidationError
我们可以看下关于Field的,BaseModel底层源码,其实也是在__init__中取配置进行扩展校验
python
import json
import re
# ==========================
# 1. 实现 Field:只是存规则的容器
# ==========================
def Field(*args, **kwargs):
"""
丐版 Field:
只做一件事:把校验规则存起来,返回给元类扫描
"""
return kwargs # 直接返回校验配置字典
# ==========================
# 2. 元类:扫描字段 + Field 规则
# ==========================
class MyBaseModelMeta(type):
def __new__(cls, name, bases, attrs):
annotations = attrs.get('__annotations__', {})
fields_info = {} # 存储:{字段名: {类型, 规则}}
# 遍历所有字段,提取 类型 + Field 校验规则
for field_name, expected_type in annotations.items():
# 获取字段默认值(如果是 Field 就会拿到规则字典)
field_config = attrs.get(field_name, {})
# 把【类型】和【校验规则】绑定在一起
fields_info[field_name] = {
"type": expected_type,
"config": field_config # 这里存 min_length/gt/regex 等规则
}
attrs['__fields__'] = fields_info # 元数据:类型 + 规则 全存这里
# ==========================
# 3. 生成 __init__:类型校验 + Field 规则校验
# ==========================
def __init__(self, data=None, **kwargs):
# 兼容字典实例化(你上一问的知识点)
if data is not None and isinstance(data, dict):
kwargs = {**data, **kwargs}
# 遍历所有字段 + 规则,逐一校验
for field_name, info in self.__fields__.items():
expected_type = info["type"]
config = info["config"]
value = kwargs.get(field_name)
# 1. 必填校验
if value is None:
raise ValueError(f"字段 {field_name} 不能为空")
# 2. 类型转换(简化版)
try:
value = expected_type(value)
except:
raise ValueError(f"{field_name} 类型错误")
# ==========================
# 核心:在这里执行 Field 校验!
# ==========================
# 字符串长度校验 min_length / max_length
if "min_length" in config:
if len(value) < config["min_length"]
raise ValueError(f"{field_name} 长度不足")
if "max_length" in config:
if len(value) > config["max_length"]
raise ValueError(f"{field_name} 超长")
# 数值范围校验 gt / lt
if "gt" in config:
if not value > config["gt"]:
raise ValueError(f"{field_name} 必须大于 {config['gt']}")
if "lt" in config:
if not value < config["lt"]:
raise ValueError(f"{field_name} 必须小于 {config['lt']}")
# 正则校验 regex
if "regex" in config:
if not re.match(config["regex"], value):
raise ValueError(f"{field_name} 格式错误")
# 赋值给实例
setattr(self, field_name, value)
# 绑定方法
attrs['__init__'] = __init__
attrs['model_dump'] = model_dump
attrs['model_validate'] = model_validate
return super().__new__(cls, name, bases, attrs)
class MyBaseModel(metaclass=MyBaseModelMeta):
pass
嵌套模型
支持模型嵌套,完美适配复杂的层级数据(如接口返回的嵌套 JSON)。
python
from pydantic import BaseModel
# 地址模型
class Address(BaseModel):
province: str
city: str
street: str
# 用户模型(嵌套 Address)
class User(BaseModel):
name: str
age: int
address: Address # 嵌套子模型
# 实例化嵌套模型
user = User(
name="张三",
age=18,
address={
"province": "广东",
"city": "深圳",
"street": "南山区科技园"
}
)
# 层级访问嵌套字段
print(user.address.city) # 输出:深圳
模型序列化:转为字典 / JSON
BaseModel 内置序列化方法,可快速将模型转为字典或JSON 字符串,适配接口返回、数据存储场景。
python
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
email: str | None = None
user = User(name="张三", age=18)
# 1. 转为字典(最常用)
user_dict = user.model_dump()
print(user_dict)
# 输出:{'name': '张三', 'age': 18, 'email': None}
# 2. 转为 JSON 字符串
user_json = user.model_dump_json()
print(user_json)
# 输出:{"name":"张三","age":18,"email":null}
# 3. 排除 None 字段
user_dict_no_none = user.model_dump(exclude_none=True)
print(user_dict_no_none)
# 输出:{'name': '张三', 'age': 18}
模型复用与继承
支持模型继承,复用已有字段,减少重复代码。
python
from pydantic import BaseModel
# 基础用户模型
class BaseUser(BaseModel):
name: str
age: int
# 管理员模型(继承 BaseUser,新增字段)
class AdminUser(BaseUser):
role: str = "admin"
permissions: list[str]
# 实例化
admin = AdminUser(name="管理员", age=30, permissions=["delete", "update"])
print(admin.role) # 输出:admin
可选字段与必传字段
- 必传字段:仅写类型注解(如
name: str); - 可选字段:类型 + None 默认值(如
email: str | None = None)。
python
class User(BaseModel):
# 必传
name: str
# 必传(无默认值)
age: int
# 可选(有默认值)
email: str | None = None
# 正确:必传字段都传了
user1 = User(name="张三", age=18)
# 错误:缺少 age 字段,抛出校验异常
# user2 = User(name="李四")
我们还是看下大概的源码,理解可选字段和必传字段的校验。
python
class MyBaseModelMeta(type):
def __new__(cls, name, bases, attrs):
annotations = attrs.get('__annotations__', {})
fields_info = {}
# 🔥 核心:元类遍历字段,同时收集【类型 + 默认值】
for field_name, field_type in annotations.items():
# 1. 从 attrs 中读取字段的默认值(就是你写的 = None)
default_value = attrs.get(field_name) # <--- 这里拿到 default
# 2. 把类型、默认值一起存入元数据
fields_info[field_name] = {
"type": field_type,
"default": default_value # <--- 存进 info,后续用 info.get("default") 读取
}
attrs['__fields__'] = fields_info
# 生成 __init__
def __init__(self, **kwargs):
for field_name, info in self.__fields__.items():
# 3. 从元数据中取出收集好的默认值
default = info.get("default")
# 没传参 → 用默认值;传了参 → 校验
value = kwargs.get(field_name, default)
if field_name in kwargs:
if not isinstance(value, info["type"]):
raise TypeError(f"{field_name} 类型错误")
setattr(self, field_name, value)
attrs['__init__'] = __init__
return super().__new__(cls, name, bases, attrs)
自定义 init 构造方法
继承 BaseModel 后可手动定义 __init__,用于实现动态默认值 等自定义初始化逻辑;Pydantic 元类不会覆盖手写构造方法,所有校验逻辑由父类BaseModel 统一处理。
核心特性
- 手写 init 不会被元类覆盖
- 必须调用 super().init() 触发 Pydantic 完整校验
- 适用场景:动态时间、空字典、动态默认参数
python
from datetime import datetime
from typing import Literal, Optional, Dict, Any
from pydantic import BaseModel
# 字面量类型定义
MessageRole = Literal["system", "user", "assistant", "tool"]
class Message(BaseModel):
content: str
role: MessageRole
timestamp: Optional[datetime] = None
metadata: Optional[Dict[str, Any]] = None
# 自定义构造方法(不会被元类覆盖)
def __init__(self, content: str, role: MessageRole, **kwargs):
# 自定义动态默认值
default_ts = datetime.now()
default_meta = {}
# 调用父类构造,触发校验、类型转换
super().__init__(
content=content,
role=role,
timestamp=kwargs.get("timestamp", default_ts),
metadata=kwargs.get("metadata", default_meta)
)
# 正常使用:自动填充动态值 + Literal校验
msg = Message(content="测试消息", role="user")
# 非法角色直接报错:msg = Message(content="测试", role="test")
我们看下BaseModel 底层更接近真实情况的源码,实际上参数校验、解析、自动数据转换等功能全部集成在BaseModel的构造函数__init__中。
python
# ======================
# 1. 模拟 Pydantic 元类:不覆盖手写 __init__!
# ======================
class MyBaseModelMeta(type):
def __new__(cls, name, bases, attrs):
# 🔥 核心:只收集字段,不覆盖 __init__!
annotations = attrs.get('__annotations__', {})
# 把字段信息存起来,给父类 __init__ 校验用
attrs['__fields__'] = annotations
# ✅ 关键:如果用户没写 __init__,才生成一个转发的!
if "__init__" not in attrs:
def auto_init(self, **kwargs):
super().__init__(**kwargs) # 只转发参数
attrs["__init__"] = auto_init
return super().__new__(cls, name, bases, attrs)
# ======================
# 2. 父类 BaseModel:所有校验都在这里!
# ======================
class MyBaseModel(metaclass=MyBaseModelMeta):
def __init__(self, **kwargs):
# 🔥 核心:所有校验逻辑都在父类 __init__ 里!
fields = self.__class__.__fields__
# 校验逻辑(Literal/类型/必填)
for field_name, field_type in fields.items():
value = kwargs[field_name]
# 模拟 Literal 校验
if hasattr(field_type, '__allowed_values'):
allowed = field_type.__allowed_values
if value not in allowed:
raise ValueError(f"{field_name} 只能是:{allowed}")
setattr(self, field_name, value)
# ======================
# 3. 你的业务类:手写 __init__,不会被覆盖!
# ======================
class Message(MyBaseModel):
role: str # 简化演示
content: str
# 手写 __init__:完全保留,不被元类覆盖
def __init__(self, content: str, role: str, **kwargs):
print("执行:你手写的 __init__")
# 调用父类 __init__ → 触发校验
super().__init__(content=content, role=role)
# ======================
# 测试
# ======================
msg = Message(content="你好", role="user")
print(msg.content)
四、BaseModel 核心方法总结
| 方法 | 作用 |
|---|---|
| model_dump() | 模型 → 字典 |
| model_dump_json() | 模型 → JSON 字符串 |
| model_validate() | 字典 / JSON → 模型实例(手动校验) |
| model_copy() | 复制模型实例(可修改部分字段) |
| fields | 获取模型所有字段的元信息(类型、校验规则) |