本文来自《AI 应用开发课程》月份 1 课程文档,已整理为网站文章版本。
学习目标
学完本节后,你应当能够:
- 理解什么时候用函数,什么时候用类。
- 理解数据类的用途。
- 读懂并书写基础类型注解。
- 为后续 Pydantic 模型学习打下基础。
前置知识
- 已完成
01-Python最小入门.md - 已完成
02-函数-模块-包.md
1. 为什么这节重要
月份 1 后面会频繁出现下面这些结构:
ChatMessageChatRequestChatResponseToolDefinition
它们本质上都是“有结构的数据”。如果你不会用类或数据类,就很难把这些对象组织清楚。
2. 类的最小示例
class ChatMessage:
def __init__(self, role: str, content: str) -> None:
self.role = role
self.content = content
def summary(self) -> str:
return f"[{self.role}] {self.content}"
使用:
message = ChatMessage(role="user", content="你好")
print(message.summary())
适合用类的场景:
- 数据和行为需要放在一起
- 对象需要长期复用
- 结构比普通字典更稳定
3. 数据类
如果一个对象主要是“装数据”,Python 提供了更简洁的写法:dataclass。
from dataclasses import dataclass
@dataclass
class ChatMessage:
role: str
content: str
这比手写 __init__ 更简洁。
你可以把数据类理解为:
- 比字典更清晰
- 比完整类更轻量
- 很适合表达“结构化数据”
4. 类型注解基础
基础写法
name: str = "leo"
age: int = 18
score: float = 99.5
函数类型
def add(x: int, y: int) -> int:
return x + y
列表和字典
messages: list[str] = ["hello", "hi"]
profile: dict[str, str] = {"name": "leo"}
可选值
from typing import Optional
nickname: Optional[str] = None
5. 为什么动态语言还要写类型注解
原因不是“为了让 Python 变成静态语言”,而是为了:
- 增强可读性
- 让编辑器和检查工具更早发现错误
- 降低多人协作成本
- 让复杂接口更容易理解
月份 1 所有关键代码都应带基本类型注解。
6. 从字典升级到结构化对象
错误示例:
message = {"role": "user", "content": "hello"}
这不是不能用,而是问题很多:
- 字段名容易拼错
- 没有清晰约束
- 没有类型提示
更好的版本:
from dataclasses import dataclass
@dataclass
class ChatMessage:
role: str
content: str
这也是后面 Pydantic 模型出现前的过渡训练。
7. 实操任务
完成以下练习:
- 用类定义一个
UserProfile,包含name、role。 - 用数据类定义一个
ChatMessage。 - 给 3 个函数补全参数和返回值类型注解。
8. 自测题
- 字典、类、数据类的差异是什么?
- 为什么
ChatMessage这种结构不建议长期用裸字典表示? - 为什么类型注解对后续 FastAPI 和 Pydantic 很关键?
9. 作业与验收
作业:
- 定义
ChatMessage和ToolCallResult两个数据类。
验收标准:
- 字段命名清晰
- 都有类型注解
- 能在一个主程序里实例化并打印
10. 常见错误
- 把
list[str]写成list(str) - 忘记写返回值类型
- 用类存数据但没有明确字段
11. 延伸阅读
- Python 官方文档中的
dataclasses - Real Python 的 type hints 教程
12. 本章与前文关系
上一章解决了“代码怎么拆模块”,这一章解决“数据怎么表达”。这一步很重要,因为月份 1 后面所有核心对象都不是随便一段字符串,而是结构化数据:
ChatMessageChatRequestChatResponseToolDefinitionToolCallResult
如果你不会用类、数据类和类型注解表达结构,后面几乎每章都会感觉别扭。
13. 本章在研发助手项目中的位置
研发助手项目中的很多关键对象,都需要从“裸字典”升级为“明确结构的对象”。比如:
- 用户传来的消息
- 模型返回的结构化结果
- 工具定义与工具结果
- API 的请求和响应
你现在学的,不是面向抽象语法题,而是面向真实项目对象建模。
14. 裸字典、普通类、数据类、Pydantic 模型的关系
这是月份 1 非常关键的一条认知主线。
裸字典
优点:
- 上手快
- 写起来少
缺点:
- 字段名容易拼错
- 没有边界
- 类型不清晰
普通类
优点:
- 可以把数据和行为放在一起
- 更适合表达一个“对象”
缺点:
- 如果只是装数据,会显得稍重
数据类
优点:
- 比普通类更轻量
- 很适合表达“主要是存数据的对象”
缺点:
- 校验能力有限
Pydantic 模型
优点:
- 有数据校验
- 错误信息清晰
- 和 FastAPI 天然配合
缺点:
- 比纯 Python 对象多一层框架依赖
月份 1 的学习路线其实就是:
裸字典 -> 普通类 / 数据类 -> Pydantic 模型
15. 错误示例 vs 正确示例
错误示例:长期用裸字典表达消息
message = {"rol": "user", "content": "hello"}
这里字段名 role 拼错成了 rol,程序未必会在第一时间发现。
正确示例:使用结构化对象
from dataclasses import dataclass
@dataclass
class ChatMessage:
role: str
content: str
这种写法至少让“消息应该有哪些字段”变得清晰了。
16. 完整文件级示例:dataclass_demo.py
"""使用普通类和数据类表达聊天消息。"""
from dataclasses import dataclass
class LegacyChatMessage:
"""用普通类表达消息。
这种写法更接近你在很多面向对象语言中的直觉。
"""
def __init__(self, role: str, content: str) -> None:
self.role = role
self.content = content
def summary(self) -> str:
return f"[{self.role}] {self.content}"
@dataclass
class ChatMessage:
"""使用数据类表达消息。
当对象主要是“装数据”时,数据类通常比手写 __init__ 更清晰。
"""
role: str
content: str
def summary(self) -> str:
return f"[{self.role}] {self.content}"
def main() -> None:
old_message = LegacyChatMessage(role="user", content="请总结这个 diff")
new_message = ChatMessage(role="assistant", content="这是摘要结果")
print(old_message.summary())
print(new_message.summary())
if __name__ == "__main__":
main()
17. 逐段解释这份完整示例
为什么保留 LegacyChatMessage
因为你可能来自 Java、C#、Go 等语言,用普通类会更容易建立迁移直觉。课程先让你看到“传统写法”,再让你看到“Python 更自然的写法”。
为什么 ChatMessage 更适合用数据类
因为它目前主要承担“数据容器”的角色,数据类能自动生成初始化逻辑,让代码更短、更清晰。
为什么仍然可以保留方法
很多人误以为数据类只能装字段。其实只要有必要,它仍然可以有方法,比如这里的 summary()。
18. 类型注解的工程意义
类型注解不是为了把 Python 变成 Java,而是为了让你在复杂系统中少犯低级错误。
以月份 1 为例,类型注解至少能帮助你做到:
- 快速理解函数输入输出
- 让编辑器给出更好的提示
- 在重构时更少踩坑
- 为后面 Pydantic 和 FastAPI 做铺垫
19. 一个更接近后续项目的增强版示例
下面是更贴近月份 1 后续风格的表达:
from dataclasses import dataclass
from typing import Literal
ChatRole = Literal["system", "user", "assistant", "tool"]
@dataclass
class ChatMessage:
role: ChatRole
content: str
这里多出来的关键点是 Literal:
- 它不是任意字符串
- 它在告诉读者:角色只能是这几个值
这就是“类型注解从可读性走向约束表达”的开始。
20. 从最小实现到工程实现的递进
你可以把本章理解成三段升级:
第一步:字典先能装数据
适合最初上手,但边界弱。
第二步:数据类让结构更清晰
适合建立对象表达习惯。
第三步:Pydantic 让结构同时具备“清晰 + 校验”
适合进入 FastAPI、API schema、结构化输出等阶段。
下一模块的 Pydantic 文档,会承接这里继续往下走。
21. 调试与排错:本章最常见问题
问题一:类型注解写法不熟
例如把:
list[str]
写成:
list(str)
问题二:把数据类当成 Pydantic
数据类会帮你自动生成 __init__,但它不会像 Pydantic 那样做强校验。
问题三:把对象结构设计得过早过重
月份 1 不需要一开始就设计很复杂的类层级。目标是先把消息、请求、结果这些核心结构表达清楚。
22. 本章完成后你应该具备的能力
完成本章后,你应当做到:
- 能解释字典、普通类、数据类的区别。
- 能为一个简单对象写出数据类。
- 能读懂常见类型注解。
- 能理解为什么月份 1 后面要升级到 Pydantic。
23. 如果你卡在这里,先回看哪几章
- 仍然不熟函数和模块:回看 02-函数-模块-包.md
- 基础 Python 语法还不熟:回看 01-Python最小入门.md
24. 从本章过渡到下一章的桥接说明
接下来进入 04-异常-文件-JSON.md。
这很自然:既然你已经学会如何表达结构化对象,下一步就该学习这些对象如何被保存、加载和在失败时正确处理。后面的 API 调用、结构化输出、日志记录,本质都离不开异常处理和 JSON。