Skip to content

第 9 章 模型导出与字段筛选 ​

前几章关注的是输入:怎样把普通字典校验成模型对象。现在顺着流程往后走一步,看看校验通过后,怎么把模型交给后续代码:

python
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 怎样筛字段,也会亲手试一遍。

字典导出 ​

示例继续使用文章详情模型。为了让重点落在导出上,代码只保留文章字段和发布摘要、评论开关两条规则,没有重复前章的其他校验逻辑。真实项目里直接在完整模型上导出就行,不需要为此再定义一套模型。

到目前为止,主线一直是:

text
普通字典 -> 模型对象

现在看下一步:

text
模型对象 -> 普通字典 / JSON 字符串

比如,后续业务函数可能只接收字典;调试时想看完整结构;接口要返回 JSON;列表页只需要标题、摘要和作者;草稿里的 summary=None 也可能不想发出去。

整个过程可以看这张图:

Pydantic 模型数据导出双通道架构

每个示例都是完整代码,可以复制到 .code/main.py 后运行;下一步再用新的示例覆盖它。

模型对象

校验成功后拿到的是模型对象,例如:

python
article = ArticleDetail.model_validate(data)

可以用属性访问它的字段:

python
article.title
article.author.username
article.comments[0].content

普通字典

需要普通字典时,调用 model_dump():

python
article_dict = article.model_dump()

导出结果要用键访问:

python
article_dict["title"]
article_dict["author"]["username"]
article_dict["comments"][0]["content"]

简单说:

text
模型对象用点号访问
普通字典用键访问

先看一次完整导出。将下面的代码保存为 .code/main.py:

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"])

运行:

bash
uv run python main.py

关键输出如下:

text
<class '__main__.ArticleDetail'>
<class 'dict'>
<class 'dict'>
<class 'list'>
<class 'dict'>
xiaoming
写得很清楚

前两行能看出 article 还是模型对象,而 article_dict 已经是字典。再往下看,嵌套的 author 变成字典,comments 变成字典列表;最后两行演示了如何读取这些嵌套值。

模型对象、字典与 JSON 字符串的访问方式

模型对象用属性访问,字典用键访问;JSON 字符串则适合打印、保存或传输。

模型导出流程

Pydantic 模型数据导出双通道架构

可以把前后的职责分开看:校验处理输入,导出处理输出。对应到代码:

article 仍是模型对象

python
article = ArticleDetail.model_validate(data)

这里得到的仍是模型对象。

model_dump() 返回普通字典

python
article_dict = article.model_dump()

调用 model_dump() 后则用键访问:

python
article_dict["title"]

嵌套模型也会一起导出

例如:

  • author: UserInfo 会变成嵌套字典
  • comments: list[CommentCreate] 会变成字典列表

嵌套模型导出结构

嵌套模型解构导出与原模型不可变性保证

model_dump() 会一路处理嵌套模型,把它们也转成普通 Python 数据结构。

字段筛选 ​

导出时可以控制哪些字段留下来。常用的三种写法是:

python
article.model_dump(include={"title", "summary", "author"})
python
article.model_dump(exclude={"comments"})
python
article.model_dump(exclude_none=True)

这三个参数分别表示:

text
include:只要谁
exclude:不要谁
exclude_none:不要值为 None 的字段

完整导出并不总合适:有时只需要几个字段,有时要排除敏感或无关字段,也可能想去掉 None。

include 指定字段 ​

运行这份代码,看看筛选结果:

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)

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"])

运行:

bash
uv run python main.py
python
basic_info = article.model_dump(include={"title", "summary", "author"})
print(basic_info)

结果里只留下:

  • title
  • summary
  • author

运行结果中的字段列表为:

text
include 字段: ['author', 'summary', 'title']

exclude 排除字段 ​

python
without_comments = article.model_dump(exclude={"comments"})
print(without_comments)

其余字段照常导出,只有 comments 被排除。

运行结果会显示:

text
exclude 后含 comments: False

exclude_none=True 排除 None 值 ​

草稿的摘要没有填写,正好可以用来观察 None 的处理:

python
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)

先看普通导出中的这一项:

python
"summary": None

在普通导出里会保留;加上 exclude_none=True 后,这个字段就会消失。运行结果还说明 0、False 和空列表仍然保留:

text
普通导出的摘要: None
exclude_none 后含 summary: False
exclude_none 后的默认值: 0 False []

注意,exclude_none=True 只移除 None。像 0、False 和空列表都会保留。

字段筛选对比

四种字段导出过滤策略矩阵

这张图把三种筛选方式放在一起,方便对照上面的结果。

JSON 导出 ​

需要 JSON 字符串时,调用 model_dump_json():

python
article_json = article.model_dump_json(indent=2)

返回值是字符串,不是字典:

text
model_dump() -> dict
model_dump_json() -> str

model_dump() 默认使用 Python 模式,返回 Python 字典。里面的值不一定都能直接放进 JSON,比如日期时间对象;要序列化成 JSON 字符串,就用 model_dump_json()。

将下面的完整代码保存为 .code/main.py:

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)

运行:

bash
uv run python main.py
python
article_json = article.model_dump_json(indent=2)

print(type(article_json))
print(article_json)

输出较长,这里截取几行:

text
<class 'str'>
  "is_published": true,
完整 JSON 含 comments: True
排除后 JSON 含 comments: False

public_json 仍是 JSON 字符串,只是少了 comments。使用时留意下面几点:

model_dump_json() 返回字符串

python
type(article_json)

它的类型是 str,因此不能像字典一样这样取值:

python
article_json["title"]

indent=2 控制缩进

python
article.model_dump_json(indent=2)

它只调整排版,不改变数据内容。

JSON 布尔值

Python 里是:

python
True
False

JSON 里会变成:

json
true
false

这是 JSON 的布尔值写法,和 Python 的 True、False 对应。

JSON 字段筛选

例如,排除评论后仍得到 JSON 字符串:

python
public_json = article.model_dump_json(exclude={"comments"}, indent=2)
print(public_json)

model_dump_json() 也支持 include、exclude 和 exclude_none。

字典与 JSON 导出对比

Pydantic 模型数据导出双通道架构

图里对比了两个方法的返回形式:一个是 dict,一个是 str。

嵌套字段排除 ​

也可以只排除嵌套对象里的一个字段。比如保留作者用户名,不导出邮箱:

为了专心看嵌套筛选,附件中的 ArticleDetail 只保留 title、content、author 和 comments。完整模型也可以用同样的 exclude 写法。

将下面的完整代码保存为 .code/main.py:

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)

运行:

bash
uv run python main.py
python
result = article.model_dump(exclude={"author": {"email"}})
print(result)

这里表示:

text
导出 author 时,排除 author.email

输出中的 author 部分为:

text
'author': {'username': 'xiaoming'}

本章先看到这里,暂时不展开更复杂的嵌套筛选。

文章导出综合案例 ​

最后把几种导出方式放进同一个例子:

将下面的完整代码保存为 .code/main.py:

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))

运行:

bash
uv run python main.py

这个例子会把同一个模型导出成字典和 JSON,也会筛掉指定字段。注意观察:这些操作只影响导出结果,不会修改原模型。

最后几行能确认这一点:

text
导出字典含作者邮箱: False
原模型作者邮箱: xiaoming@example.com
导出字典含评论: False
原模型评论数: 1

输出显示邮箱和评论已从导出字典中排除,但原模型里的邮箱和评论都还在。

本章小结 ​

导出时先看自己要什么类型:model_dump() 给 Python 字典,model_dump_json() 给 JSON 字符串。嵌套模型也会跟着导出,两种方法都能配合字段筛选参数。exclude_none=True 只去掉值为 None 的字段。