第 9 章 模型导出与字段筛选
前几章关注的是输入:怎样把普通字典校验成模型对象。现在顺着流程往后走一步,看看校验通过后,怎么把模型交给后续代码:
article = ArticleDetail.model_validate(data)实际开发中,模型对象还会被继续处理,比如:
- 转成普通字典,交给后续业务逻辑
- 转成 JSON 字符串,方便打印、保存或传输
- 只导出部分字段
- 排除某些字段
- 不导出值为
None的字段
本章会用到两个方法:
model_dump()model_dump_json()
再用 include、exclude 和 exclude_none=True 挑选要导出的内容。
本章学习目标
模型校验好了,接下来要把数据交给别的代码或接口。你会分清模型对象、普通字典和 JSON 字符串,用 model_dump() 导出字典,用 model_dump_json() 导出 JSON。嵌套模型导出后长什么样,以及 include、exclude、exclude_none=True 怎样筛字段,也会亲手试一遍。
字典导出
示例继续使用文章详情模型。为了让重点落在导出上,代码只保留文章字段和发布摘要、评论开关两条规则,没有重复前章的其他校验逻辑。真实项目里直接在完整模型上导出就行,不需要为此再定义一套模型。
到目前为止,主线一直是:
普通字典 -> 模型对象现在看下一步:
模型对象 -> 普通字典 / JSON 字符串比如,后续业务函数可能只接收字典;调试时想看完整结构;接口要返回 JSON;列表页只需要标题、摘要和作者;草稿里的 summary=None 也可能不想发出去。
整个过程可以看这张图:
每个示例都是完整代码,可以复制到 .code/main.py 后运行;下一步再用新的示例覆盖它。
模型对象
校验成功后拿到的是模型对象,例如:
article = ArticleDetail.model_validate(data)可以用属性访问它的字段:
article.title
article.author.username
article.comments[0].content普通字典
需要普通字典时,调用 model_dump():
article_dict = article.model_dump()导出结果要用键访问:
article_dict["title"]
article_dict["author"]["username"]
article_dict["comments"][0]["content"]简单说:
模型对象用点号访问
普通字典用键访问先看一次完整导出。将下面的代码保存为 .code/main.py:
from pydantic import BaseModel, Field, field_validator, model_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
class CommentCreate(BaseModel):
username: str = Field(min_length=2, max_length=20)
content: str = Field(min_length=2, max_length=200)
likes: int = Field(default=0, ge=0)
class ArticleDetail(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
summary: str | None = Field(default=None, max_length=100)
is_published: bool = Field(default=False)
allow_comments: bool = Field(default=True)
views: int = Field(default=0, ge=0)
tags: list[str] = Field(default_factory=list)
author: UserInfo
comments: list[CommentCreate] = Field(default_factory=list)
@model_validator(mode="after")
def validate_article_rules(self):
if self.is_published and not self.summary:
raise ValueError("文章发布时必须填写摘要")
if not self.allow_comments and self.comments:
raise ValueError("关闭评论时,评论列表必须为空")
return self
data = {
"title": "Pydantic 模型导出",
"content": "这是一篇介绍 Pydantic 模型导出的文章内容",
"summary": "介绍 model_dump 和 model_dump_json",
"is_published": True,
"allow_comments": True,
"views": 100,
"tags": ["Python", "Pydantic"],
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
"comments": [
{
"username": "alice",
"content": "写得很清楚",
"likes": 3,
}
],
}
article = ArticleDetail.model_validate(data)
article_dict = article.model_dump()
print(type(article))
print(type(article_dict))
print(type(article_dict["author"]))
print(type(article_dict["comments"]))
print(type(article_dict["comments"][0]))
print(article_dict["author"]["username"])
print(article_dict["comments"][0]["content"])运行:
uv run python main.py关键输出如下:
<class '__main__.ArticleDetail'>
<class 'dict'>
<class 'dict'>
<class 'list'>
<class 'dict'>
xiaoming
写得很清楚前两行能看出 article 还是模型对象,而 article_dict 已经是字典。再往下看,嵌套的 author 变成字典,comments 变成字典列表;最后两行演示了如何读取这些嵌套值。
模型对象、字典与 JSON 字符串的访问方式
模型对象用属性访问,字典用键访问;JSON 字符串则适合打印、保存或传输。
模型导出流程
可以把前后的职责分开看:校验处理输入,导出处理输出。对应到代码:
article 仍是模型对象
article = ArticleDetail.model_validate(data)这里得到的仍是模型对象。
model_dump() 返回普通字典
article_dict = article.model_dump()调用 model_dump() 后则用键访问:
article_dict["title"]嵌套模型也会一起导出
例如:
author: UserInfo会变成嵌套字典comments: list[CommentCreate]会变成字典列表
嵌套模型导出结构
model_dump() 会一路处理嵌套模型,把它们也转成普通 Python 数据结构。
字段筛选
导出时可以控制哪些字段留下来。常用的三种写法是:
article.model_dump(include={"title", "summary", "author"})article.model_dump(exclude={"comments"})article.model_dump(exclude_none=True)这三个参数分别表示:
include:只要谁
exclude:不要谁
exclude_none:不要值为 None 的字段完整导出并不总合适:有时只需要几个字段,有时要排除敏感或无关字段,也可能想去掉 None。
include 指定字段
运行这份代码,看看筛选结果:
from pydantic import BaseModel, Field, field_validator, model_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
class CommentCreate(BaseModel):
username: str = Field(min_length=2, max_length=20)
content: str = Field(min_length=2, max_length=200)
likes: int = Field(default=0, ge=0)
class ArticleDetail(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
summary: str | None = Field(default=None, max_length=100)
is_published: bool = Field(default=False)
allow_comments: bool = Field(default=True)
views: int = Field(default=0, ge=0)
tags: list[str] = Field(default_factory=list)
author: UserInfo
comments: list[CommentCreate] = Field(default_factory=list)
@model_validator(mode="after")
def validate_article_rules(self):
if self.is_published and not self.summary:
raise ValueError("文章发布时必须填写摘要")
if not self.allow_comments and self.comments:
raise ValueError("关闭评论时,评论列表必须为空")
return self
data = {
"title": "Pydantic 模型导出",
"content": "这是一篇介绍 Pydantic 模型导出的文章内容",
"summary": "介绍 model_dump 和 model_dump_json",
"is_published": True,
"allow_comments": True,
"views": 100,
"tags": ["Python", "Pydantic"],
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
"comments": [
{
"username": "alice",
"content": "写得很清楚",
"likes": 3,
}
],
}
draft_data = {
"title": "Pydantic 草稿文章",
"content": "这是一篇还没有发布的草稿文章内容",
"is_published": False,
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}
article = ArticleDetail.model_validate(data)
draft_article = ArticleDetail.model_validate(draft_data)
basic_info = article.model_dump(include={"title", "summary", "author"})
without_comments = article.model_dump(exclude={"comments"})
draft_dict = draft_article.model_dump()
draft_without_none = draft_article.model_dump(exclude_none=True)
print(basic_info)
print(without_comments)
print(draft_dict)
print(draft_without_none)
print("include 字段:", sorted(basic_info))
print("exclude 后含 comments:", "comments" in without_comments)
print("普通导出的摘要:", draft_dict["summary"])
print("exclude_none 后含 summary:", "summary" in draft_without_none)
print("exclude_none 后的默认值:", draft_without_none["views"], draft_without_none["is_published"], draft_without_none["tags"])运行:
uv run python main.pybasic_info = article.model_dump(include={"title", "summary", "author"})
print(basic_info)结果里只留下:
titlesummaryauthor
运行结果中的字段列表为:
include 字段: ['author', 'summary', 'title']exclude 排除字段
without_comments = article.model_dump(exclude={"comments"})
print(without_comments)其余字段照常导出,只有 comments 被排除。
运行结果会显示:
exclude 后含 comments: Falseexclude_none=True 排除 None 值
草稿的摘要没有填写,正好可以用来观察 None 的处理:
draft_data = {
"title": "Pydantic 草稿文章",
"content": "这是一篇还没有发布的草稿文章内容",
"is_published": False,
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}
draft_article = ArticleDetail.model_validate(draft_data)
draft_dict = draft_article.model_dump()
draft_without_none = draft_article.model_dump(exclude_none=True)
print(draft_dict)
print(draft_without_none)先看普通导出中的这一项:
"summary": None在普通导出里会保留;加上 exclude_none=True 后,这个字段就会消失。运行结果还说明 0、False 和空列表仍然保留:
普通导出的摘要: None
exclude_none 后含 summary: False
exclude_none 后的默认值: 0 False []注意,exclude_none=True 只移除 None。像 0、False 和空列表都会保留。
字段筛选对比
这张图把三种筛选方式放在一起,方便对照上面的结果。
JSON 导出
需要 JSON 字符串时,调用 model_dump_json():
article_json = article.model_dump_json(indent=2)返回值是字符串,不是字典:
model_dump() -> dict
model_dump_json() -> strmodel_dump() 默认使用 Python 模式,返回 Python 字典。里面的值不一定都能直接放进 JSON,比如日期时间对象;要序列化成 JSON 字符串,就用 model_dump_json()。
将下面的完整代码保存为 .code/main.py:
from pydantic import BaseModel, Field, field_validator, model_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
class CommentCreate(BaseModel):
username: str = Field(min_length=2, max_length=20)
content: str = Field(min_length=2, max_length=200)
likes: int = Field(default=0, ge=0)
class ArticleDetail(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
summary: str | None = Field(default=None, max_length=100)
is_published: bool = Field(default=False)
allow_comments: bool = Field(default=True)
views: int = Field(default=0, ge=0)
tags: list[str] = Field(default_factory=list)
author: UserInfo
comments: list[CommentCreate] = Field(default_factory=list)
@model_validator(mode="after")
def validate_article_rules(self):
if self.is_published and not self.summary:
raise ValueError("文章发布时必须填写摘要")
if not self.allow_comments and self.comments:
raise ValueError("关闭评论时,评论列表必须为空")
return self
data = {
"title": "Pydantic 模型导出",
"content": "这是一篇介绍 Pydantic 模型导出的文章内容",
"summary": "介绍 model_dump 和 model_dump_json",
"is_published": True,
"allow_comments": True,
"views": 100,
"tags": ["Python", "Pydantic"],
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
"comments": [
{
"username": "alice",
"content": "写得很清楚",
"likes": 3,
}
],
}
article = ArticleDetail.model_validate(data)
article_json = article.model_dump_json(indent=2)
public_json = article.model_dump_json(exclude={"comments"}, indent=2)
print(type(article_json))
print(article_json)
print(public_json)
print("完整 JSON 含 comments:", '"comments"' in article_json)
print("排除后 JSON 含 comments:", '"comments"' in public_json)运行:
uv run python main.pyarticle_json = article.model_dump_json(indent=2)
print(type(article_json))
print(article_json)输出较长,这里截取几行:
<class 'str'>
"is_published": true,
完整 JSON 含 comments: True
排除后 JSON 含 comments: Falsepublic_json 仍是 JSON 字符串,只是少了 comments。使用时留意下面几点:
model_dump_json() 返回字符串
type(article_json)它的类型是 str,因此不能像字典一样这样取值:
article_json["title"]indent=2 控制缩进
article.model_dump_json(indent=2)它只调整排版,不改变数据内容。
JSON 布尔值
Python 里是:
True
FalseJSON 里会变成:
true
false这是 JSON 的布尔值写法,和 Python 的 True、False 对应。
JSON 字段筛选
例如,排除评论后仍得到 JSON 字符串:
public_json = article.model_dump_json(exclude={"comments"}, indent=2)
print(public_json)model_dump_json() 也支持 include、exclude 和 exclude_none。
字典与 JSON 导出对比
图里对比了两个方法的返回形式:一个是 dict,一个是 str。
嵌套字段排除
也可以只排除嵌套对象里的一个字段。比如保留作者用户名,不导出邮箱:
为了专心看嵌套筛选,附件中的 ArticleDetail 只保留 title、content、author 和 comments。完整模型也可以用同样的 exclude 写法。
将下面的完整代码保存为 .code/main.py:
from pydantic import BaseModel, Field
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
class CommentCreate(BaseModel):
username: str = Field(min_length=2, max_length=20)
content: str = Field(min_length=2, max_length=200)
likes: int = Field(default=0, ge=0)
class ArticleDetail(BaseModel):
title: str
content: str
author: UserInfo
comments: list[CommentCreate] = Field(default_factory=list)
data = {
"title": "Pydantic 模型导出",
"content": "这是一篇介绍嵌套字段排除的文章内容",
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
"comments": [
{
"username": "alice",
"content": "写得很清楚",
"likes": 3,
}
],
}
article = ArticleDetail.model_validate(data)
result = article.model_dump(exclude={"author": {"email"}})
print(result)运行:
uv run python main.pyresult = article.model_dump(exclude={"author": {"email"}})
print(result)这里表示:
导出 author 时,排除 author.email输出中的 author 部分为:
'author': {'username': 'xiaoming'}本章先看到这里,暂时不展开更复杂的嵌套筛选。
文章导出综合案例
最后把几种导出方式放进同一个例子:
将下面的完整代码保存为 .code/main.py:
from pydantic import BaseModel, Field, field_validator, model_validator
class UserInfo(BaseModel):
username: str = Field(min_length=2, max_length=20)
email: str
@field_validator("username", mode="before")
@classmethod
def validate_username(cls, value: object) -> object:
if not isinstance(value, str):
return value
value = value.strip()
if " " in value:
raise ValueError("用户名不能包含空格")
return value
class CommentCreate(BaseModel):
username: str = Field(min_length=2, max_length=20)
content: str = Field(min_length=2, max_length=200)
likes: int = Field(default=0, ge=0)
class ArticleDetail(BaseModel):
title: str = Field(min_length=2, max_length=50)
content: str = Field(min_length=10)
summary: str | None = Field(default=None, max_length=100)
is_published: bool = Field(default=False)
allow_comments: bool = Field(default=True)
views: int = Field(default=0, ge=0)
tags: list[str] = Field(default_factory=list)
author: UserInfo
comments: list[CommentCreate] = Field(default_factory=list)
@model_validator(mode="after")
def validate_article_rules(self):
if self.is_published and not self.summary:
raise ValueError("文章发布时必须填写摘要")
if not self.allow_comments and self.comments:
raise ValueError("关闭评论时,评论列表必须为空")
return self
data = {
"title": "Pydantic 模型导出",
"content": "这是一篇介绍 Pydantic 模型导出的文章内容",
"summary": "介绍 model_dump 和 model_dump_json",
"is_published": True,
"allow_comments": True,
"views": 100,
"tags": ["Python", "Pydantic"],
"author": {
"username": " xiaoming ",
"email": "xiaoming@example.com",
},
"comments": [
{
"username": "alice",
"content": "写得很清楚",
"likes": 3,
}
],
}
draft_data = {
"title": "Pydantic 草稿文章",
"content": "这是一篇还没有发布的草稿文章内容",
"is_published": False,
"author": {
"username": "xiaoming",
"email": "xiaoming@example.com",
},
}
article = ArticleDetail.model_validate(data)
draft_article = ArticleDetail.model_validate(draft_data)
article_dict = article.model_dump()
basic_info = article.model_dump(include={"title", "summary", "author"})
without_comments = article.model_dump(exclude={"comments"})
without_author_email = article.model_dump(exclude={"author": {"email"}})
article_json = article.model_dump_json(indent=2)
draft_dict_without_none = draft_article.model_dump(exclude_none=True)
print(type(article))
print(type(article_dict))
print(type(article_json))
print(article_dict["author"]["username"])
print(basic_info)
print(without_comments)
print(without_author_email)
print(article_json)
print(draft_dict_without_none)
print("导出字典含作者邮箱:", "email" in without_author_email["author"])
print("原模型作者邮箱:", article.author.email)
print("导出字典含评论:", "comments" in without_comments)
print("原模型评论数:", len(article.comments))运行:
uv run python main.py这个例子会把同一个模型导出成字典和 JSON,也会筛掉指定字段。注意观察:这些操作只影响导出结果,不会修改原模型。
最后几行能确认这一点:
导出字典含作者邮箱: False
原模型作者邮箱: xiaoming@example.com
导出字典含评论: False
原模型评论数: 1输出显示邮箱和评论已从导出字典中排除,但原模型里的邮箱和评论都还在。
本章小结
导出时先看自己要什么类型:model_dump() 给 Python 字典,model_dump_json() 给 JSON 字符串。嵌套模型也会跟着导出,两种方法都能配合字段筛选参数。exclude_none=True 只去掉值为 None 的字段。