类型标注与数据模型
类型标注与数据模型 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
这一课把「字典乱传」升级成「对象有形」:内部对象用类型标注和 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
两者的分工一条线划清:
- dataclass 管内部:函数之间传递的业务对象,形状稳定、来源可信,类型提示 + 静态检查(mypy/pyright)就够。
- Pydantic 管边界:HTTP 请求体、JSON 文件、环境变量这类「不信任的外部输入」,必须运行时校验。P08 的接口会直接用
NoteIn做请求体。 - 标注是文档:
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 不行,是它压根没被赋予校验职责。混淆两者职责的代码,就是接口漏洞的来源。
动手任务
- 给 P03 的
parse_record定义返回类型为 dataclassStudyRecord,函数体改为构造该对象。 - 写
RecordIn的变体,允许minutes为 None(未记录),校验通过后转成 0。 - 对
StudyRecord(course="X")手动赋r.minutes = "abc",观察不报错——验证「dataclass 不做运行时校验」这句话。
常见错误
- 以为类型提示会拦住坏数据:
x: int只是标注,运行时什么也不检查;外部输入必须过 Pydantic。 - 校验和清洗混在一层:Pydantic 负责「拒收非法」,业务默认值该在模型之外补;一层只干一件事。
- ** everywhere dict**:函数签名全是 dict,重构靠人肉全局搜索;至少给跨模块传递的数据定型。
- Pydantic 模型写太厚:把业务规则全塞进 Field 校验器,报错信息和业务脱节;边界校验管形状,业务规则留业务层。
检查与复盘
自查清单:
- 公共函数都有参数和返回值标注
- 能一句话说清 dataclass 和 Pydantic 各自的职责边界
- 接口、文件、环境变量进来的数据都经过运行时校验模型
参考资料
- typing 标准库文档 —— 类型标注语法与常用泛型
- Pydantic 官方文档:Models —— 模型定义与 ValidationError
读完这一课了?
标记完成后,工作台会安排接下来的学习。