P05第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

类型标注与数据模型

类型标注与数据模型 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

这一课把「字典乱传」升级成「对象有形」:内部对象用类型标注和 dataclass 描述形状,外部输入用 Pydantic 做运行时校验,并分清「类型提示」和「真校验」的边界。

问题场景

P02 处理的记录是裸 dict:r["minutes"] 拼错成 r["mintues"],只有跑炸了才知道。TS 里你靠 interface + zod 解决同一个问题;Python 的分工完全对应——类型提示是给人和工具看的(TS 类型),Pydantic 是运行时验证器(zod)。

关键认知先立住:类型提示在运行时不生效。往 def f(x: int) 传字符串,Python 照样执行,炸不炸看函数体。所以「内部代码信类型提示,外部输入必须真校验」。

核心解释

内部对象用 dataclass:形状声明 + 构造检查,轻量且零依赖。

from dataclasses import dataclass

@dataclass
class StudyRecord:
    course: str
    minutes: int = 0
    done: bool = False

r = StudyRecord(course="P07", minutes=25)
print(r)                      # StudyRecord(course='P07', minutes=25, done=False)

外部输入用 Pydantic——它会在构造时真的检查每个字段,非法直接抛 ValidationError,等价于 zod 的 safeParse:

from pydantic import BaseModel, Field
from typing import Literal

class NoteIn(BaseModel):
    title: str = Field(min_length=1, max_length=60)
    body: str = Field(max_length=5000)
    tag: Literal["course", "note", "review"] = "note"   # 枚举,白名单之外一律拒绝

NoteIn.model_validate({"title": "", "body": "x"})
# ValidationError: title — String should have at least 1 character

两者的分工一条线划清:

  1. dataclass 管内部:函数之间传递的业务对象,形状稳定、来源可信,类型提示 + 静态检查(mypy/pyright)就够。
  2. Pydantic 管边界:HTTP 请求体、JSON 文件、环境变量这类「不信任的外部输入」,必须运行时校验。P08 的接口会直接用 NoteIn 做请求体。
  3. 标注是文档:list[StudyRecord] 比 list 传达的信息多一个量级;别写无标注的公共函数。

Literal 是标注枚举的最轻写法;字段多了、要复用了再换 enum.Enum。

示例拆解

同一段 JSON,两条路径对照运行:

# main.py
from dataclasses import dataclass
from pydantic import ValidationError, BaseModel, Field

class RecordIn(BaseModel):
    course: str
    minutes: int = Field(default=0, ge=0, le=600)

if __name__ == "__main__":
    r = RecordIn.model_validate({"course": "p07", "minutes": 25})
    print(r)                                  # course='p07' minutes=25
    r2 = RecordIn.model_validate({"course": "p08", "minutes": 99999})
    # ValidationError: minutes — Input should be less than or equal to 600

预期结果:合法数据通过;minutes=99999 被拒绝,错误信息指出字段和越界原因。注意 dataclass 版本不会拦 minutes=99999——这不是 dataclass 不行,是它压根没被赋予校验职责。混淆两者职责的代码,就是接口漏洞的来源。

动手任务

  1. 给 P03 的 parse_record 定义返回类型为 dataclass StudyRecord,函数体改为构造该对象。
  2. 写 RecordIn 的变体,允许 minutes 为 None(未记录),校验通过后转成 0。
  3. 对 StudyRecord(course="X") 手动赋 r.minutes = "abc",观察不报错——验证「dataclass 不做运行时校验」这句话。

常见错误

  • 以为类型提示会拦住坏数据:x: int 只是标注,运行时什么也不检查;外部输入必须过 Pydantic。
  • 校验和清洗混在一层:Pydantic 负责「拒收非法」,业务默认值该在模型之外补;一层只干一件事。
  • ** everywhere dict**:函数签名全是 dict,重构靠人肉全局搜索;至少给跨模块传递的数据定型。
  • Pydantic 模型写太厚:把业务规则全塞进 Field 校验器,报错信息和业务脱节;边界校验管形状,业务规则留业务层。

检查与复盘

自查清单:

  • 公共函数都有参数和返回值标注
  • 能一句话说清 dataclass 和 Pydantic 各自的职责边界
  • 接口、文件、环境变量进来的数据都经过运行时校验模型

参考资料

检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

Python 3.11+ 中,函数参数可能是字符串也可能是 None,类型标注的正确写法是?

Q2多选中等(多选,全部选对才算正确)

关于 Python 3.11+ 的类型标注,下列说法正确的有哪些?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...