为什么需要类型注解?
Python 作为动态语言,灵活但容易在运行时暴露类型错误。类型注解(Type Hints)自 Python 3.5 引入,结合 IDE 和静态检查工具(如 mypy、pyright),能在编码阶段发现潜在问题。Pydantic 更进一步,利用注解在运行时进行数据验证和解析,特别适合 API 开发、配置管理等场景。
基础回顾:函数注解与变量注解
# 函数注解
def greet(name: str, age: int = 18) -> str:
return f"Hello {name}, you are {age} years old."
# 变量注解
name: str = "Alice"
count: int = 10
注意:注解仅在静态检查时生效,运行时不会强制类型。
进阶用法
1. 泛型与容器类型
from typing import List, Dict, Tuple, Optional, Union, Any
# 列表
def process_items(items: List[int]) -> None:
for item in items:
print(item)
# 字典
def lookup(data: Dict[str, int], key: str) -> Optional[int]:
return data.get(key)
# 可选类型
def parse(value: Optional[str] = None) -> str:
return value if value is not None else "default"
# 联合类型
def handle(value: Union[int, float]) -> float:
return float(value)
2. TypedDict:字典结构定义
from typing import TypedDict
class Person(TypedDict):
name: str
age: int
email: str
def create_person(person: Person) -> None:
print(person["name"])
# 使用
p: Person = {"name": "Bob", "age": 30, "email": "bob@example.com"}
create_person(p)
注意:TypedDict 是运行时普通的 dict,但静态检查会校验字段。
3. Literal:精确值约束
from typing import Literal
def set_mode(mode: Literal["auto", "manual"]) -> str:
return f"Mode set to {mode}"
set_mode("auto") # 正确
set_mode("semi") # mypy 报错
4. NewType:创建新类型
from typing import NewType
UserId = NewType("UserId", int)
user_id: UserId = UserId(123)
def get_user(uid: UserId) -> str:
return f"User {uid}"
get_user(user_id) # 正确
get_user(123) # 静态检查报错,但运行时正常
5. Protocol:鸭子类型
from typing import Protocol
class Flyable(Protocol):
def fly(self) -> None: ...
class Bird:
def fly(self) -> None:
print("Flying")
def let_it_fly(obj: Flyable) -> None:
obj.fly()
let_it_fly(Bird()) # 正确,不需要继承
Pydantic:运行时验证
Pydantic 利用类型注解在运行时进行数据验证和序列化,特别适合处理 JSON 输入。
安装
pip install pydantic
基础模型
from pydantic import BaseModel, Field, ValidationError
class User(BaseModel):
id: int
name: str = Field(..., min_length=1, max_length=50)
age: int = Field(..., ge=0, le=150)
email: str | None = None
# 有效数据
try:
user = User(id=1, name="Alice", age=30, email="alice@example.com")
print(user.model_dump()) # {'id': 1, 'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
except ValidationError as e:
print(e)
# 无效数据
try:
User(id="abc", name="", age=200)
except ValidationError as e:
print(e.errors())
# [{'type': 'int_parsing', ...}, {'type': 'string_too_short', ...}, {'type': 'greater_than_equal', ...}]
提示:Pydantic 会自动进行类型转换(如字符串转数字),但严格模式可禁用。
高级验证:自定义验证器
from pydantic import BaseModel, field_validator
class PasswordModel(BaseModel):
password: str
confirm_password: str
@field_validator("confirm_password")
@classmethod
def passwords_match(cls, v, info):
if "password" in info.data and v != info.data["password"]:
raise ValueError("passwords do not match")
return v
嵌套模型与复杂类型
from pydantic import BaseModel
from typing import List, Optional
class Address(BaseModel):
street: str
city: str
zip_code: str
class Company(BaseModel):
name: str
address: Address
employees: List[User]
# 从 JSON 解析
data = {
"name": "Tech Corp",
"address": {"street": "123 Main St", "city": "NYC", "zip_code": "10001"},
"employees": [
{"id": 1, "name": "Alice", "age": 30},
{"id": 2, "name": "Bob", "age": 25}
]
}
company = Company(**data)
print(company.model_dump_json(indent=2))
配置与性能
from pydantic import BaseModel, ConfigDict
class FastModel(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
name: str
# extra="forbid" 禁止额外字段
# frozen=True 使实例不可变
实战:API 数据验证
结合 FastAPI 使用 Pydantic 模型作为请求体验证:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., min_length=1)
price: float = Field(..., gt=0)
is_offer: bool = False
@app.post("/items/")
async def create_item(item: Item):
return {"message": f"Item {item.name} created", "price": item.price}
# 启动:uvicorn main:app --reload
踩坑经验
- 运行时类型检查:类型注解默认不检查运行时,需用 Pydantic 或
typeguard库。 - Optional 与默认值:
Optional[str]等价于Union[str, None],但默认值需显式写None。 - Pydantic V2 变化:V2 使用
modeldump()替代dict(),modelvalidate()替代parse_obj()。 - 性能考虑:Pydantic 验证有开销,对高性能场景可使用
@pydantic.dataclass或跳过验证。
总结
本文从基础类型注解到 Pydantic 实战,涵盖了 Python 类型系统的核心进阶用法。类型注解不仅提升代码可读性,还能通过静态检查减少错误,而 Pydantic 则将其扩展到运行时,实现数据验证、序列化和配置管理。下一步可以探索 mypy 配置、Pydantic 的 Field 函数高级选项,以及如何结合 dataclasses 使用。