Python 类型注解进阶:从入门到 Pydantic

By | 2026年7月27日

为什么需要类型注解?

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

踩坑经验

  1. 运行时类型检查:类型注解默认不检查运行时,需用 Pydantic 或 typeguard 库。
  2. Optional 与默认值Optional[str] 等价于 Union[str, None],但默认值需显式写 None
  3. Pydantic V2 变化:V2 使用 modeldump() 替代 dict()modelvalidate() 替代 parse_obj()
  4. 性能考虑:Pydantic 验证有开销,对高性能场景可使用 @pydantic.dataclass 或跳过验证。

总结

本文从基础类型注解到 Pydantic 实战,涵盖了 Python 类型系统的核心进阶用法。类型注解不仅提升代码可读性,还能通过静态检查减少错误,而 Pydantic 则将其扩展到运行时,实现数据验证、序列化和配置管理。下一步可以探索 mypy 配置、Pydantic 的 Field 函数高级选项,以及如何结合 dataclasses 使用。

延伸阅读