Skip to content

第 1 章 Pydantic 与输入校验 ​

Python 项目里,经常要处理外部输入数据。

例如:文章提交到程序后,我们拿到的可能是一份普通字典:

python
data = {
    "title": "Pydantic 入门",
    "content": "这是一篇文章",
    "views": "100"
}

这份数据乍看没问题,views 却是字符串。字段也可能缺失,或者把 views 写成 view。如果程序直接拿它做业务处理,问题常常要等到读取或计算时才冒出来。

这一章就来解决一件事:怎样先检查输入,再把可靠的数据交给后面的代码?

本章学习目标 ​

普通字典用起来方便,但业务代码直接拿它当可靠数据,会遇到什么问题?这一章先把风险看清,再用 BaseModel 描述数据结构。学完后,你能用 model_validate() 校验字典、读取模型对象,也能看懂基础的 ValidationError。

普通字典的使用风险 ​

dict 很适合保存键值对,但它不会替我们检查字段是否齐全、名字是否写对,或值的类型是否符合预期。我们先不用 Pydantic,看看问题会在哪一步出现。

按课程首页的说明创建本地 .code 练习目录,把下面代码复制到 .code/main.py:

py
data = {
    "title": "Pydantic 入门",
    "content": "这是一篇文章",
    "views": "100",
}

print("文章标题:", data["title"])
print("文章内容:", data["content"])
print("阅读量:", data["views"])
print("阅读量类型:", type(data["views"]))

try:
    new_views = data["views"] + 1
    print("新的阅读量:", new_views)
except TypeError as e:
    print("阅读量计算失败:", e)


bad_data = {
    "title": "Pydantic 入门",
    "view": "100",
}

try:
    print("错误数据中的文章内容:", bad_data["content"])
except KeyError as e:
    print("字段不存在:", e)

try:
    print("错误数据中的阅读量:", bad_data["views"])
except KeyError as e:
    print("字段不存在:", e)

运行:

bash
uv run python main.py

可能看到类似输出:

text
文章标题: Pydantic 入门
文章内容: 这是一篇文章
阅读量: 100
阅读量类型: <class 'str'>
阅读量计算失败: can only concatenate str (not "int") to str
字段不存在: 'content'
字段不存在: 'views'

第一份数据里的 views 是字符串,和整数相加时才报错。第二份数据少了 content,还把 views 拼成了 view;代码读取必需字段时就遇到了 KeyError。字典保存了值,却没有保证这些值符合业务要求。

数据模型与输入校验 ​

普通字典 vs BaseModel 对比

对文章数据来说,我们希望 title、content、views 都存在,而且 views 是整数。数据模型就是把这类结构要求写清楚的一种方式:

text
title   -> str
content -> str
views   -> int

Pydantic 提供了 BaseModel,可以用它定义模型,再把输入交给模型校验。校验通过后,拿到的是模型对象,后续可以通过属性访问字段。

用 BaseModel 描述文章数据 ​

还是刚才的文章数据。把 .code/main.py 替换成下面的代码:

py
from pydantic import BaseModel


class ArticleCreate(BaseModel):
    title: str
    content: str
    views: int


data = {
    "title": "Pydantic 入门",
    "content": "这是一篇文章",
    "views": "100",
}

article = ArticleCreate.model_validate(data)

print(article)
print(article.title)
print(article.content)
print(article.views)
print(type(article.views))

运行:

bash
uv run python main.py

可能看到类似输出:

text
title='Pydantic 入门' content='这是一篇文章' views=100
Pydantic 入门
这是一篇文章
100
<class 'int'>

模型类 ArticleCreate 声明了字段和类型要求:

python
class ArticleCreate(BaseModel):
    title: str
    content: str
    views: int

用 model_validate() 校验输入 ​

调用 ArticleCreate.model_validate(data) 后,Pydantic 会检查输入是否符合模型要求。校验成功,才会返回模型对象:

python
article = ArticleCreate.model_validate(data)

默认情况下,模型没有声明的额外字段会被忽略。如果把必需字段名写错,比如输入里只有 view,模型仍会发现 views 缺失,校验不会通过。

访问模型字段 ​

python
print(article.title)
print(article.views)

这时 article 是模型对象,原始的 data 仍是普通字典。注意,输入中的 "100" 原本是字符串;因为它可以转换为整数,校验后 article.views 的值就是 100。类型注解是校验规则的一部分,但并不意味着所有类型不匹配都会被直接拒绝。

输入校验错误 ​

可转换的 "100" 能通过校验。如果 content 缺失,或 views 是无法转成整数的 "很多",Pydantic 会抛出 ValidationError。把 .code/main.py 换成下面的代码,分别观察这两种错误:

py
from pydantic import BaseModel, ValidationError


class ArticleCreate(BaseModel):
    title: str
    content: str
    views: int


missing_data = {
    "title": "Pydantic 入门",
    "views": "100",
}

try:
    article = ArticleCreate.model_validate(missing_data)
    print(article)
except ValidationError as e:
    print("校验失败:")
    print(e)


wrong_type_data = {
    "title": "Pydantic 入门",
    "content": "这是一篇文章",
    "views": "很多",
}

try:
    article = ArticleCreate.model_validate(wrong_type_data)
    print(article)
except ValidationError as e:
    print("校验失败:")
    print(e)

运行 uv run python main.py。错误信息可能包含其他细节,先找到出错字段和原因就够了:

text
校验失败:
1 validation error for ArticleCreate
content
  Field required

校验失败:
1 validation error for ArticleCreate
views
  Input should be a valid integer

这里的两处问题分别是:content 没有提供,views 无法转换为整数。记住这条顺序:

text
输入字典 -> model_validate()
          ├─ 校验成功:得到模型对象,再读取 article.title、article.views
          └─ 校验失败:抛出 ValidationError,处理错误后再继续

model_validate 校验流程

最后用一个脚本把成功和失败两种情况放在一起看。再次替换 .code/main.py,运行 uv run python main.py:

py
from pydantic import BaseModel, ValidationError


class ArticleCreate(BaseModel):
    title: str
    content: str
    views: int


data = {
    "title": "Pydantic 入门",
    "content": "这是一篇文章",
    "views": "100",
}

try:
    article = ArticleCreate.model_validate(data)
    print("文章数据校验成功")
    print("文章标题:", article.title)
    print("文章内容:", article.content)
    print("阅读量:", article.views)
    print("阅读量类型:", type(article.views))
except ValidationError as e:
    print("文章数据校验失败")
    print(e)


bad_data = {
    "title": "Pydantic 入门",
    "views": "很多",
}

try:
    article = ArticleCreate.model_validate(bad_data)
    print("文章数据校验成功")
    print("文章标题:", article.title)
    print("文章内容:", article.content)
    print("阅读量:", article.views)
except ValidationError as e:
    print("文章数据校验失败")
    print(e)

运行后会先看到成功校验的文章数据,views 已经是整数;随后错误数据会校验失败。第二份数据既缺少 content,views 也无法转成整数,因此错误信息会指出这两个字段的问题。

本章小结 ​

普通字典只负责装数据,数据是否可靠还得自己确认。用 BaseModel 定好结构,交给 model_validate() 校验;成功后再使用模型对象,失败时查看 ValidationError。先校验,再使用,后面几章都会沿着这条线走。