Skip to content

第 11 章 模型配置与输入行为 ​

前面几章分别处理了字段、校验和导出。本章再看模型整体的行为,比如:

  • 输入里多了模型没声明的字段,应该忽略、报错,还是保留?
  • 输入不是字典,而是普通对象时,能不能从对象属性里取值?
  • 文章标题、正文带有前后空格时,能不能统一清理?

这些设置不属于单个字段,本章会用 model_config 来配置。

先看三个常用配置:

  • extra
  • from_attributes
  • str_strip_whitespace

本章学习目标 ​

一条条字段规则之外,模型本身也有处理输入的方式。这章会用 ConfigDict 设置 model_config,比较 extra="ignore"、"forbid"、"allow",试着用 from_attributes=True 读取对象属性、用 str_strip_whitespace=True 清理字符串空白。学完还要分得清模型配置、字段规则、校验器和序列化器各自管哪一段。

模型配置基础 ​

继续用文章模型做例子。

先看一份常见输入:

python
data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 model_config 的文章内容",
    "views": 100,
    "unknown_field": "这个字段模型里没有声明",
}

unknown_field 不在模型字段里。遇到这种情况,可以选择:

  • 忽略它
  • 禁止它,直接报错
  • 保留它

数据也不一定总是字典,有时拿到的是普通对象:

python
article_object.title
article_object.content
article_object.views

另外,标题和正文可能带有前后空格。字段未知处理、对象属性读取和字符串清理,这些都属于模型整体行为:

Pydantic 字段规则与全局模型配置职责分工图

每个示例都可以复制到 .code/main.py 运行;进入下一步时再用新代码覆盖它。

额外字段的默认处理 ​

先不写配置,看看默认结果。把下面的代码保存为 .code/main.py:

py
from pydantic import BaseModel, Field


class ArticleDetail(BaseModel):
    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 model_config 的文章内容",
    "views": 100,
    "unknown_field": "这个字段模型里没有声明",
}

article = ArticleDetail.model_validate(data)

print(article)
print(article.model_dump())
print("默认导出含 unknown_field:", "unknown_field" in article.model_dump())

运行:

bash
uv run python main.py

关键输出是:

text
默认导出含 unknown_field: False

输入里虽然有 unknown_field,模型对象和导出字典都没有它。Pydantic 默认按 extra="ignore" 处理,后面再看怎样切换这个行为。

model_config 的作用

在 Pydantic v2 里,model_config 用来设置模型整体行为:

例如:

python
from pydantic import BaseModel, ConfigDict


class ArticleDetail(BaseModel):
    model_config = ConfigDict(extra="forbid")

这条配置会作用于整个模型:输入里只要出现未声明字段,就会报错。

ConfigDict 配置写法

配置值用 ConfigDict 声明:

python
from pydantic import ConfigDict

再放进模型类的 model_config:

python
model_config = ConfigDict(extra="forbid")

或者:

python
model_config = ConfigDict(from_attributes=True)

本课程统一使用 Pydantic v2 主线,不把旧版 class Config 作为主线写法。

extra 的取值

extra 决定输入中那些“模型没有声明的字段”怎么处理:

配置含义
extra="ignore"忽略多余字段
extra="forbid"禁止多余字段,出现就报错
extra="allow"允许并保留多余字段

模型配置位置与执行流程

Pydantic 字段规则与全局模型配置职责分工图

model_config 和字段、校验器一样,写在模型类定义里。

默认额外字段处理流程

Pydantic 额外字段 extra 模式三路对比与决策树

禁止额外字段 ​

如果希望输入更严格,可以把 extra 设为 forbid:

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

py
from pydantic import BaseModel, ConfigDict, Field, ValidationError


class ArticleDetail(BaseModel):
    model_config = ConfigDict(extra="forbid")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 model_config 的文章内容",
    "views": 100,
    "unknown_field": "这个字段模型里没有声明",
}

try:
    article = ArticleDetail.model_validate(data)
    print(article)
except ValidationError as error:
    print(error)
    print("错误类型:", error.errors()[0]["type"])

运行:

bash
uv run python main.py
python
from pydantic import BaseModel, ConfigDict, Field, ValidationError


class ArticleDetail(BaseModel):
    model_config = ConfigDict(extra="forbid")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 model_config 的文章内容",
    "views": 100,
    "unknown_field": "这个字段模型里没有声明",
}

try:
    article = ArticleDetail.model_validate(data)
    print(article)
except ValidationError as error:
    print(error)
    print("错误类型:", error.errors()[0]["type"])

关键配置是:

python
model_config = ConfigDict(extra="forbid")

extra="forbid" 只影响未声明字段的处理方式,不会要求显式传入带默认值的字段。模型必需字段是否缺失,仍按字段本身的定义校验。

运行结果中的关键行是:

text
错误类型: extra_forbidden

适合这些情况:

  • 你希望拒绝模型未声明的字段
  • 你想尽早发现拼写错误
  • 你不想悄悄吞掉未知字段

extra="forbid" 处理流程

Pydantic 额外字段 extra 模式三路对比与决策树

额外字段模式对比 ​

再看另外两种选择:忽略,或保留。

extra="ignore" 忽略额外字段 ​

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

py
from pydantic import BaseModel, ConfigDict, Field


class ArticleIgnore(BaseModel):
    model_config = ConfigDict(extra="ignore")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)


class ArticleAllow(BaseModel):
    model_config = ConfigDict(extra="allow")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)


data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 extra 模式的文章内容",
    "unknown_field": "这是模型未声明的字段",
}

article_ignore = ArticleIgnore.model_validate(data)
print(article_ignore)
print(article_ignore.model_dump())

article_allow = ArticleAllow.model_validate(data)
print(article_allow)
print(article_allow.model_dump())
print(article_allow.unknown_field)
print("ignore 含 unknown_field:", "unknown_field" in article_ignore.model_dump())
print("allow 含 unknown_field:", "unknown_field" in article_allow.model_dump())

运行:

bash
uv run python main.py
python
from pydantic import BaseModel, ConfigDict, Field


class ArticleIgnore(BaseModel):
    model_config = ConfigDict(extra="ignore")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)


data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 extra 模式的文章内容",
    "unknown_field": "这是模型未声明的字段",
}

article_ignore = ArticleIgnore.model_validate(data)

print(article_ignore)
print(article_ignore.model_dump())

多余字段不会报错,也不会保留。

extra="allow" 保留额外字段 ​

python
from pydantic import BaseModel, ConfigDict, Field


class ArticleAllow(BaseModel):
    model_config = ConfigDict(extra="allow")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)


data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 extra 模式的文章内容",
    "unknown_field": "这是模型未声明的字段",
}

article_allow = ArticleAllow.model_validate(data)

print(article_allow)
print(article_allow.model_dump())
print(article_allow.unknown_field)

多余字段会进入模型并保留下来。

两个模型共用同一份 data。看末尾的检查结果:

text
ignore 含 unknown_field: False
allow 含 unknown_field: True

若再用 forbid 处理同一份数据,则会报 extra_forbidden。

三种 extra 模式对比

Pydantic 额外字段 extra 模式三路对比与决策树

三种模式也可以这样区分:

text
ignore:不要它,但不报错
forbid:不要它,而且报错
allow:要它,并保留

入门时通常先用 ignore 或 forbid。allow 会放宽模型边界,只有确实要保留未知字段时再选它。

对象属性读取 ​

from_attributes=True 的作用

平时最常见的输入是字典:

python
ArticleDetail.model_validate(data)

如果输入换成普通对象,可以设置:

python
model_config = ConfigDict(from_attributes=True)

这样 Pydantic 就会从对象属性读取字段值。下面用同一个对象比较默认行为和开启配置后的结果:

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

py
from pydantic import BaseModel, ConfigDict, Field, ValidationError


class ArticleObject:
    def __init__(self, title: str, content: str, views: int):
        self.title = title
        self.content = content
        self.views = views


class ArticleDefault(BaseModel):
    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleDetail(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


article_object = ArticleObject(
    title="Pydantic 对象转换",
    content="这是一篇介绍 from_attributes 的文章内容",
    views=100,
)

try:
    ArticleDefault.model_validate(article_object)
except ValidationError as error:
    print("未启用 from_attributes:", error.errors()[0]["type"])

article = ArticleDetail.model_validate(article_object)

print("启用 from_attributes 后:", article.title)
print(article)
print(article.title)
print(article.model_dump())

运行:

bash
uv run python main.py
python
from pydantic import BaseModel, ConfigDict, Field, ValidationError


class ArticleObject:
    def __init__(self, title: str, content: str, views: int):
        self.title = title
        self.content = content
        self.views = views


class ArticleDefault(BaseModel):
    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleDetail(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


article_object = ArticleObject(
    title="Pydantic 对象转换",
    content="这是一篇介绍 from_attributes 的文章内容",
    views=100,
)

try:
    ArticleDefault.model_validate(article_object)
except ValidationError as error:
    print("未启用 from_attributes:", error.errors()[0]["type"])

article = ArticleDetail.model_validate(article_object)

print("启用 from_attributes 后:", article.title)
print(article)
print(article.title)
print(article.model_dump())

对比结果中的关键两行是:

text
未启用 from_attributes: model_type
启用 from_attributes 后: Pydantic 对象转换

默认模型不能把 article_object 当作字典校验;启用配置后,就会从它的属性读取 title、content 和 views。

对应的配置是:

python
model_config = ConfigDict(from_attributes=True)

配置表示:输入是对象时,尝试从对象属性读取字段。本节只演示普通对象,不展开 ORM。

对象属性读取流程

Pydantic 对象属性读取 from_attributes 机制流转图

字符串空白清理 ​

再把 model_config 和前面几种工具放在一起对照:

工具主要解决什么问题
字段类型字段是什么类型
Field字段有哪些基础规则
field_validator单字段输入校验
model_validator多字段联合校验
field_serializer字段导出格式
model_config模型整体行为

文章表单里的标题、正文可能带有前后空格。若想统一清理模型中的字符串字段,可以设置 str_strip_whitespace=True。

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

py
from pydantic import BaseModel, ConfigDict


class ArticleDetail(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)

    title: str
    content: str


data = {
    "title": "  Pydantic 模型配置  ",
    "content": "  这是一篇介绍 str_strip_whitespace 的文章内容  ",
}

article = ArticleDetail.model_validate(data)

print("输入标题:", repr(data["title"]))
print("清理后标题:", repr(article.title))
print("输入正文:", repr(data["content"]))
print("清理后正文:", repr(article.content))
print(article.title)
print(article.content)

运行:

bash
uv run python main.py

运行结果中的 repr() 会把前后空格显示出来:

text
输入标题: '  Pydantic 模型配置  '
清理后标题: 'Pydantic 模型配置'
输入正文: '  这是一篇介绍 str_strip_whitespace 的文章内容  '
清理后正文: '这是一篇介绍 str_strip_whitespace 的文章内容'

开启后,字符串字段会自动去掉前后空白。示例只演示两个字段,不再扩展其他配置项。

Pydantic 全局字符串空白清理机制

文章模型配置案例 ​

最后用一个例子把本章的配置放在一起:

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

py
from pydantic import BaseModel, ConfigDict, Field, ValidationError


class ArticleIgnore(BaseModel):
    model_config = ConfigDict(extra="ignore")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleForbid(BaseModel):
    model_config = ConfigDict(extra="forbid")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleAllow(BaseModel):
    model_config = ConfigDict(extra="allow")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleObject:
    def __init__(self, title: str, content: str, views: int):
        self.title = title
        self.content = content
        self.views = views


class ArticleFromObject(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleStrip(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)

    title: str
    content: str


extra_data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 model_config 的文章内容",
    "views": 100,
    "unknown_field": "这个字段模型里没有声明",
}

article_ignore = ArticleIgnore.model_validate(extra_data)
print(article_ignore.model_dump())
print("ignore 含 unknown_field:", "unknown_field" in article_ignore.model_dump())

try:
    article_forbid = ArticleForbid.model_validate(extra_data)
    print(article_forbid)
except ValidationError as error:
    print(error)
    print("forbid 错误类型:", error.errors()[0]["type"])

article_allow = ArticleAllow.model_validate(extra_data)
print(article_allow.model_dump())
print(article_allow.unknown_field)
print("allow 含 unknown_field:", "unknown_field" in article_allow.model_dump())

article_object = ArticleObject(
    title="Pydantic 对象转换",
    content="这是一篇介绍 from_attributes 的文章内容",
    views=200,
)

article_from_object = ArticleFromObject.model_validate(article_object)
print(article_from_object.model_dump())

strip_data = {
    "title": "  Pydantic 模型配置  ",
    "content": "  这是一篇介绍字符串清理的文章内容  ",
}
article_strip = ArticleStrip.model_validate(strip_data)
print("清理前标题:", repr(strip_data["title"]))
print("清理后标题:", repr(article_strip.title))

运行:

bash
uv run python main.py
python
from pydantic import BaseModel, ConfigDict, Field, ValidationError


class ArticleIgnore(BaseModel):
    model_config = ConfigDict(extra="ignore")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleForbid(BaseModel):
    model_config = ConfigDict(extra="forbid")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleAllow(BaseModel):
    model_config = ConfigDict(extra="allow")

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleObject:
    def __init__(self, title: str, content: str, views: int):
        self.title = title
        self.content = content
        self.views = views


class ArticleFromObject(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    title: str = Field(min_length=2, max_length=50)
    content: str = Field(min_length=10)
    views: int = Field(default=0, ge=0)


class ArticleStrip(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)

    title: str
    content: str


extra_data = {
    "title": "Pydantic 模型配置",
    "content": "这是一篇介绍 model_config 的文章内容",
    "views": 100,
    "unknown_field": "这个字段模型里没有声明",
}

article_ignore = ArticleIgnore.model_validate(extra_data)
print(article_ignore.model_dump())
print("ignore 含 unknown_field:", "unknown_field" in article_ignore.model_dump())

try:
    article_forbid = ArticleForbid.model_validate(extra_data)
    print(article_forbid)
except ValidationError as error:
    print(error)
    print("forbid 错误类型:", error.errors()[0]["type"])

article_allow = ArticleAllow.model_validate(extra_data)
print(article_allow.model_dump())
print(article_allow.unknown_field)
print("allow 含 unknown_field:", "unknown_field" in article_allow.model_dump())

article_object = ArticleObject(
    title="Pydantic 对象转换",
    content="这是一篇介绍 from_attributes 的文章内容",
    views=200,
)

article_from_object = ArticleFromObject.model_validate(article_object)
print(article_from_object.model_dump())

strip_data = {
    "title": "  Pydantic 模型配置  ",
    "content": "  这是一篇介绍字符串清理的文章内容  ",
}
article_strip = ArticleStrip.model_validate(strip_data)
print("清理前标题:", repr(strip_data["title"]))
print("清理后标题:", repr(article_strip.title))

这里把三个配置放进各自的模型里,依次处理额外字段、对象属性读取和字符串空白。

关键输出可以对照为:

text
ignore 含 unknown_field: False
forbid 错误类型: extra_forbidden
allow 含 unknown_field: True
清理前标题: '  Pydantic 模型配置  '
清理后标题: 'Pydantic 模型配置'

extra 模式选择 ​

遇到多余字段时,可以按这个顺序选择:

Pydantic 额外字段 extra 模式三路对比与决策树

实际选择时:

  • 输入边界严格时,优先考虑 forbid
  • 只关心声明字段时,使用 ignore
  • 只有确实需要保留未知字段时,再考虑 allow

本章小结 ​

model_config = ConfigDict(...) 管模型整体行为。多出来的字段怎么处理,看 extra;从对象属性取值,用 from_attributes=True;清理字符串前后空白,用 str_strip_whitespace=True。具体字段的类型和约束,仍写在字段声明里。