Pydantic v2 进阶:field_validator / EmailStr / model_config
一句话:Pydantic v2 是 Pydantic 的第二个主要版本,提供了更好的性能(Rust 核心)、更强大的验证功能、更灵活的配置。在 FastAPI 中,Pydantic 用于数据验证、序列化和文档生成。
1. Pydantic v2 新特性
1.1 性能提升
graph LR A[Pydantic v1] -->|Python核心| B[性能一般] C[Pydantic v2] -->|Rust核心| D[性能提升10-50倍] style C fill:#e8f5e8 style D fill:#e1f5fe
-
Rust 核心:验证逻辑用 Rust 实现,比纯 Python 快 10-50 倍
-
更好的错误信息:更精确的错误定位
-
JSON Schema 支持:更标准的 JSON Schema 生成
1.2 安装
pip install pydantic>=2.0
# 或
pip install "pydantic[email]" # 包含 EmailStr 支持
2. field_validator
2.1 基础用法
from pydantic import BaseModel, field_validator
class User(BaseModel):
name: str
age: int
email: str
@field_validator('name')
@classmethod
def validate_name(cls, v):
if len(v) < 2:
raise ValueError('姓名至少2个字符')
return v.title() # 转换为首字母大写
@field_validator('age')
@classmethod
def validate_age(cls, v):
if v < 0 or v > 150:
raise ValueError('年龄必须在0-150之间')
return v
# 使用
user = User(name="alice", age=25, email="alice@example.com")
print(user.name) # Alice(自动转换)
2.2 多字段验证
from pydantic import BaseModel, field_validator, model_validator
class PasswordChange(BaseModel):
old_password: str
new_password: str
confirm_password: str
@field_validator('new_password')
@classmethod
def validate_new_password(cls, v):
if len(v) < 8:
raise ValueError('新密码至少8位')
if not any(c.isupper() for c in v):
raise ValueError('新密码必须包含大写字母')
return v
@model_validator(mode='after')
def validate_passwords_match(self):
if self.new_password != self.confirm_password:
raise ValueError('两次密码不一致')
return self
3. EmailStr
3.1 基础用法
from pydantic import BaseModel, EmailStr
class User(BaseModel):
name: str
email: EmailStr # 自动验证邮箱格式
# 使用
user = User(name="Alice", email="alice@example.com") # ✅
# user = User(name="Alice", email="invalid") # ❌ ValidationError
3.2 自定义邮箱验证
from pydantic import BaseModel, EmailStr, field_validator
class User(BaseModel):
name: str
email: EmailStr
@field_validator('email')
@classmethod
def validate_email(cls, v):
# 只允许特定域名
allowed_domains = ['example.com', 'company.org']
domain = v.split('@')[1]
if domain not in allowed_domains:
raise ValueError(f'只允许{allowed_domains}域名')
return v.lower() # 转换为小写
4. model_config
4.1 基础配置
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
model_config = ConfigDict(
str_strip_whitespace=True, # 自动去除字符串前后空格
str_lower=True, # 字符串自动转小写
validate_default=True, # 验证默认值
extra='forbid', # 禁止额外字段
frozen=True, # 实例不可变
)
name: str
age: int = 0
email: str
# 使用
user = User(name=" Alice ", age=25, email="ALICE@EXAMPLE.COM")
print(user.name) # alice(自动去空格+转小写)
print(user.email) # alice@example.com
4.2 从环境变量加载
from pydantic import BaseModel, ConfigDict
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
model_config = ConfigDict(
env_file='.env',
env_prefix='APP_', # 环境变量前缀
env_file_encoding='utf-8',
)
database_url: str
api_key: str
debug: bool = False
# APP_DEBUG=true
settings = Settings()
print(settings.database_url)
5. SettingsConfigDict(FastAPI配置)
5.1 基础用法
from fastapi import FastAPI
from pydantic_settings import BaseSettings
from pydantic import ConfigDict
class Settings(BaseSettings):
model_config = ConfigDict(
env_file='.env',
env_prefix='FASTAPI_',
)
app_name: str = "My App"
debug: bool = False
database_url: str
secret_key: str
app = FastAPI()
settings = Settings()
@app.get("/")
async def root():
return {"app_name": settings.app_name}
5.2 嵌套配置
from pydantic import BaseModel
from pydantic_settings import BaseSettings
class DatabaseConfig(BaseModel):
url: str
pool_size: int = 10
max_overflow: int = 20
class RedisConfig(BaseModel):
url: str
max_connections: int = 50
class Settings(BaseSettings):
model_config = ConfigDict(
env_file='.env',
env_nested_delimiter='__', # 嵌套分隔符
)
app_name: str = "My App"
database: DatabaseConfig
redis: RedisConfig
# REDIS__MAX_CONNECTIONS=50
settings = Settings()
print(settings.database.url)
6. 与 FastAPI 集成
6.1 请求体验证
from fastapi import FastAPI
from pydantic import BaseModel, field_validator, EmailStr
app = FastAPI()
class UserCreate(BaseModel):
name: str
email: EmailStr
password: str
@field_validator('name')
@classmethod
def validate_name(cls, v):
if len(v) < 2:
raise ValueError('姓名至少2个字符')
return v
@field_validator('password')
@classmethod
def validate_password(cls, v):
if len(v) < 8:
raise ValueError('密码至少8位')
return v
@app.post("/users/")
async def create_user(user: UserCreate):
return {"user": user.model_dump()}
6.2 响应模型
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserResponse(BaseModel):
model_config = ConfigDict(from_attributes=True) # ORM模式
id: int
name: str
email: EmailStr
is_active: bool
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
# 从数据库获取用户
user = get_user_from_db(user_id)
return user # 自动转换为UserResponse
7. 高级特性
7.1 自定义类型
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
class PositiveInt:
"""正整数类型"""
@classmethod
def __get_pydantic_core_schema__(
cls, source_type, handler
) -> core_schema.CoreSchema:
return core_schema.no_info_plain_validator_function(
cls.validate,
serialization=core_schema.plain_serializer_function_ser_schema(
lambda v: v
),
)
@classmethod
def __get_pydantic_json_schema__(
cls, schema, handler
) -> JsonSchemaValue:
return {"type": "integer", "minimum": 1}
@classmethod
def validate(cls, v):
if not isinstance(v, int):
raise TypeError('必须是整数')
if v <= 0:
raise ValueError('必须是正整数')
return v
class Product(BaseModel):
name: str
price: PositiveInt # 使用自定义类型
7.2 模型继承
from pydantic import BaseModel
class BaseUser(BaseModel):
name: str
email: str
class UserCreate(BaseUser):
password: str
class UserResponse(BaseUser):
id: int
is_active: bool
# UserResponse 包含 name, email, id, is_active
8. 常见坑点
1. 字段顺序
class Bad(BaseModel):
b: int
a: str = "default"
# Bad(1) # ❌ TypeError
Bad(b=1) # ✅
# 解决:使用默认值或Optional
class Good(BaseModel):
a: str = "default"
b: int
2. 可变默认值
class Bad(BaseModel):
items: list = [] # ❌ 所有实例共享同一个list
class Good(BaseModel):
items: list = [] # ✅ Pydantic会自动处理
3. 性能考虑
# 建议:复用模型定义,不要在循环中动态创建
核心要点
from pydantic import BaseModel, field_validator, EmailStr
from pydantic import ConfigDict
# 基础模型
class User(BaseModel):
name: str
email: EmailStr
# 字段验证
@field_validator('name')
@classmethod
def validate_name(cls, v):
if len(v) < 2:
raise ValueError('姓名至少2个字符')
return v
# 模型配置
model_config = ConfigDict(
str_strip_whitespace=True,
extra='forbid',
frozen=True,
)
# FastAPI集成
@app.post("/users/")
async def create_user(user: UserCreate):
return user.model_dump()
▶ 对应原理:03-Pydantic数据校验与v2新特性
速记卡(面试闪卡)
Q1:一句话讲清「Pydantic v2 进阶:field_validator / EmailStr / model_config」到底是什么?
A:Pydantic v2 是 FastAPI 背后的数据校验引擎:用 Rust 核心提速,靠 validator、EmailStr、ConfigDict 管数据。
Q2:一、v2 新特性与性能 —— 怎么理解? —— 怎么理解?
A:像把人工审核换成机器流水线:v1 用纯 Python 核心,v2 把验证逻辑用 Rust 重写,快 10–50 倍,错误信息更准,JSON Schema 更标准。安装 pydantic>=2.0,要邮箱校验加 [email] extra。英文:Rust core / JSON Schema。
Q3:二、field_validator 字段校验 —— 怎么理解? —— 怎么理解?
A:像填表时的格式检查:用 @field_validator(‘name’) 在字段上挂校验,@classmethod 里返回清洗后的值(如转大写、拦截短姓名);多字段联动用 @model_validator(mode=‘after’),比如两次密码必须一致。英文:field_validator / model_validator。
Q4:三、EmailStr 与自定义校验 —— 怎么理解? —— 怎么理解?
A:像表单里的”邮箱格式”专用框:EmailStr 直接声明就自动校验格式,非法值抛 ValidationError。想更严(只允许某域名)就在 field_validator 里再切分域名判断。注意 application/json 本身不校验,需显式类型。英文:EmailStr / ValidationError。
Q5:四、model_config 与配置 —— 怎么理解? —— 怎么理解?
A:像给模型定”班规”:ConfigDict 里设 str_strip_whitespace 去空格、extra=‘forbid’ 拒多余字段、frozen=True 实例不可变、validate_default 校验默认值;配环境变量用 pydantic_settings 的 BaseSettings,env_prefix 前缀匹配。英文:ConfigDict / BaseSettings。
Q6:核心速记主线有哪些?
-
本质:Pydantic v2 是 Rust 核心的数据校验/序列化引擎,FastAPI 用其验参
-
性能:比 v1 快 10–50 倍,错误更准、JSON Schema 更标准
-
校验:field_validator 单字段、model_validator 跨字段(如密码一致)
-
类型:EmailStr 自动验邮箱,可叠加 field_validator 限域名
-
配置:ConfigDict 管去空格/禁额外字段/不可变;BaseSettings 读环境变量
口诀
A:Pydantic v2 换 Rust 心,校验快过旧时辰;
field_validator 把关紧,邮箱格式 EmailStr 认;
ConfigDict 立班规,禁冗去空 frozen 稳;
FastAPI 靠它验,数据干净少烦闷。
相关链接
-
📋 目录:00-FastAPI
-
📚 学习清单: Web
-
🔗 依赖注入