📌 本章目标
- • 学习 Annotated、Literal、Union 等高级类型
- • 了解 TypedDict 在 LangGraph 中的应用
1️⃣ 什么是 TypedDict?
问题背景
Python 的普通字典没有类型约束:
# 普通字典 - 没有类型检查person = { "name": "张三", "age": 25, "email": "zhangsan@example.com"}# 这些操作都不会报错,但可能导致问题person["age"] = "二十五" # 类型错误:应该是 intperson["phone"] = 12345 # 添加了未定义的字段
TypedDict 的作用
TypedDict 为字典提供了类型约束:
from typing_extensions import TypedDictclass Person(TypedDict): name: str age: int email: str# 类型检查器会提示错误person: Person = { "name": "张三", "age": "二十五", # ❌ 类型错误:应该是 int # "email": "xxx" # ❌ 缺少必需字段}
2️⃣ 基本用法
定义方式
from typing_extensions import TypedDict# 方式1: 使用 class 语法(推荐)class Person(TypedDict): name: str age: int email: str# 方式2: 使用函数语法PersonDict = TypedDict('PersonDict', { 'name': str, 'age': int, 'email': str})
创建和使用
# 创建实例person: Person = { 'name': '张三', 'age': 25, 'email': 'zhangsan@example.com'}# 访问数据(就像普通字典)print(person['name']) # 张三print(person['age']) # 25# 修改数据person['age'] = 26# 遍历for key, value in person.items(): print(f"{key}: {value}")
3️⃣ 必需字段和可选字段
默认行为
默认情况下,所有字段都是必需的:
class Person(TypedDict): name: str # 必需 age: int # 必需 email: str # 必需# ❌ 缺少字段会报错person: Person = {"name": "张三"} # 缺少 age 和 email
方式1:total=False
让所有字段都可选:
class OptionalPerson(TypedDict, total=False): name: str # 可选 age: int # 可选 email: str # 可选# ✅ 可以只提供部分字段person: OptionalPerson = {"name": "张三"}
方式2:组合继承(推荐)
分别定义必需和可选字段:
class UserRequired(TypedDict): """必需字段""" id: int username: strclass UserOptional(TypedDict, total=False): """可选字段""" email: str phone: str avatar: strclass User(UserRequired, UserOptional): """组合:必需 + 可选""" pass# ✅ 只提供必需字段user1: User = {"id": 1, "username": "alice"}# ✅ 提供所有字段user2: User = { "id": 2, "username": "bob", "email": "bob@example.com", "phone": "13800138000"}
方式3:Optional 类型
表示字段可以是 None:
from typing import Optionalclass UserWithNone(TypedDict): id: int username: str phone: Optional[str] # 可以是 str 或 Noneuser: UserWithNone = { "id": 1, "username": "alice", "phone": None # ✅ 明确设置为 None}
4️⃣ Annotated:添加元数据
基本用法
from typing import Annotatedclass UserProfile(TypedDict): # 基础字段 + 说明 user_id: Annotated[int, "用户唯一标识符"] username: Annotated[str, "用户名,3-20个字符"] # 带验证规则的字段 age: Annotated[int, "年龄,必须在0-150之间"] phone: Annotated[str, "手机号,格式: 13800138000"]
在 LangGraph 中的应用
from typing import Annotatedimport operatorclass ScoreState(TypedDict): # 使用 Annotated 添加 Reducer score: Annotated[int, operator.add] # 累加 actions: Annotated[list[str], operator.add] # 列表合并
5️⃣ Literal:限制取值范围
基本用法
from typing import Literalclass OrderStatus(TypedDict): order_id: str # 只能是指定的几个值 status: Literal['pending', 'processing', 'shipped', 'delivered', 'cancelled'] payment_method: Literal['credit_card', 'alipay', 'wechat', 'cash']# ✅ 正确order: OrderStatus = { "order_id": "ORD-001", "status": "processing", "payment_method": "alipay"}# ❌ 错误:status 值不在允许范围内order2: OrderStatus = { "order_id": "ORD-002", "status": "unknown", # ❌ 不在 Literal 中 "payment_method": "alipay"}
在 LangGraph 中的应用
class CustomerServiceState(TypedDict): priority: Literal["low", "medium", "high"] # 优先级 category: Literal["technical", "refund", "general"] # 问题类别
6️⃣ Union:多类型选择
基本用法
from typing import Union, Literalclass TextMessage(TypedDict): type: Literal['text'] content: strclass ImageMessage(TypedDict): type: Literal['image'] url: str width: int height: intclass VideoMessage(TypedDict): type: Literal['video'] url: str duration: int# 消息可以是以上任意一种类型Message = Union[TextMessage, ImageMessage, VideoMessage]def process_message(msg: Message): if msg['type'] == 'text': print(f"文本消息: {msg['content']}") elif msg['type'] == 'image': print(f"图片消息: {msg['url']}") elif msg['type'] == 'video': print(f"视频消息: {msg['url']}")
7️⃣ 嵌套 TypedDict
定义嵌套结构
class Address(TypedDict): street: str city: str country: strclass PersonWithAddress(TypedDict): name: str age: int address: Address # 嵌套另一个 TypedDict# 创建实例person: PersonWithAddress = { 'name': '王五', 'age': 28, 'address': { 'street': '中关村大街1号', 'city': '北京', 'country': '中国' }}# 访问嵌套数据print(person['address']['city']) # 北京
8️⃣ 实际应用示例
API 响应
class APIError(TypedDict): code: int message: strclass APIResponse(TypedDict): success: bool data: dict error: APIErrordef create_success_response(data: dict) -> APIResponse: return { 'success': True, 'data': data, 'error': {'code': 0, 'message': ''} }def create_error_response(code: int, message: str) -> APIResponse: return { 'success': False, 'data': {}, 'error': {'code': code, 'message': message} }
购物车系统
class CartItem(TypedDict): product_id: int name: str price: float quantity: intclass ShoppingCart(TypedDict): user_id: int items: list[CartItem] total: floatdef calculate_total(cart: ShoppingCart) -> float: return sum(item['price'] * item['quantity'] for item in cart['items'])
配置管理
class DatabaseConfig(TypedDict): host: str port: int database: str username: str password: strclass AppConfig(TypedDict): app_name: str debug: bool database: DatabaseConfigconfig: AppConfig = { 'app_name': 'MyApp', 'debug': True, 'database': { 'host': 'localhost', 'port': 3306, 'database': 'myapp_db', 'username': 'root', 'password': 'password' }}
9️⃣ TypedDict vs 其他类型
📊 LangGraph 中的 TypedDict
State 定义
from typing_extensions import TypedDictfrom typing import Annotatedimport operatorclass MyState(TypedDict): # 简单字段 name: str count: int # 带 Reducer 的字段 score: Annotated[int, operator.add] history: Annotated[list[str], operator.add] # Literal 类型 status: Literal["pending", "processing", "done"] # 可选字段 error: Optional[str]
MessagesState
from langgraph.graph import MessagesStateclass ChatState(MessagesState): """继承 MessagesState,自动包含 messages 字段""" user_name: str conversation_count: int
🎯 小结
| |
|---|
| TypedDict | |
| total=False | |
| 组合继承 | |
| Annotated | |
| Literal | |
| Union | |
🎉 教程完成!
恭喜你完成了 LangGraph 完整教程的学习!
学习回顾