Python 高性能Web框架神器 FastAPI:自动生成API文、基于Pydant、异步请求处理全搞定
用于快速构建高性能、类型安全的RESTful API。
1. 为什么 FastAPI 值得你花一个下午学会
如果你写过 Flask,大概率经历过这些事:请求参数靠 request.json.get() 手动取,类型错了要自己写 if 判断,接口文档靠手写 Swagger 或者干脆不写,前端来问字段格式时你翻代码回复。FastAPI 就是来终结这些重复劳动的。
它建立在两个成熟库之上:Starlette 负责 ASGI 异步处理和路由,Pydantic 负责数据验证和序列化。FastAPI 在它们之上做了一层声明式封装,让你用 Python 类型注解直接描述接口契约。写完之后,你免费得到三样东西:自动生成的交互式 API 文档、请求/响应的严格类型校验、原生 async 支持。
真实用途很明确:快速构建高性能、类型安全的 RESTful API。无论是给前端提供 JSON 接口、给内部系统做微服务、还是给机器学习模型套一层推理 API,FastAPI 都能在几十行代码内跑起来。
先看一个最小可运行例子,感受一下它的写法:
python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello FastAPI"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
启动命令:
bash
uvicorn main:app --reload
访问 http://127.0.0.1:8000/items/5?q=abc,返回 {"item_id": 5, "q": "abc"}。注意 item_id 声明为 int,如果你访问 /items/abc,FastAPI 会自动返回 422 错误并说明原因------这就是 Pydantic 校验在起作用,你一行校验代码都没写。
2. 安装与项目初始化
安装本身很简单,但选对依赖组合能省很多事。
bash
# 最小安装
pip install fastapi
# 推荐:带上标准依赖(含 uvicorn、httpx、jinja2 等)
pip install "fastapi[standard]"
# 或者手动装 ASGI 服务器
pip install fastapi uvicorn[standard]
uvicorn[standard] 会额外装上 uvloop(更快的异步事件循环)和 httptools(更快的 HTTP 解析),生产环境性能提升明显。
启动方式有两种。命令行方式:
bash
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
main 是模块名(main.py),app 是模块里 FastAPI 实例的变量名。--reload 只在开发时用,它会监听文件变化自动重启,生产环境务必去掉。
也可以直接在代码里启动:
python
import uvicorn
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
一个常见坑:如果你把实例命名为 application 而不是 app,启动命令要写成 uvicorn main:application。名字必须对应。
推荐的目录结构(中小项目够用):
project/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 实例与路由注册
│ ├── models.py # Pydantic 模型
│ ├── routers/ # 按业务拆分的路由
│ └── dependencies.py # 依赖注入函数
├── requirements.txt
└── tests/
3. 核心对象:FastAPI、APIRouter 与 Pydantic 模型
FastAPI 实例
FastAPI() 是整个应用的入口,它的构造参数决定了文档长什么样:
python
from fastapi import FastAPI
app = FastAPI(
title="订单服务 API",
description="处理订单创建、查询与取消",
version="1.0.0",
docs_url="/docs", # Swagger UI 地址
redoc_url="/redoc", # ReDoc 地址
)
title、description、version 会直接显示在 /docs 页面顶部。docs_url=None 可以关闭文档(生产环境不想暴露时有用)。
APIRouter:按业务拆分
当接口超过十几个,全塞在 main.py 里会失控。APIRouter 就是路由分组工具:
python
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["用户"])
@router.get("/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}
在 main.py 里挂载:
python
from app.routers import users
app.include_router(users.router)
prefix 统一加前缀,tags 让文档按分组展示。这是真实项目里最常用的组织方式。
Pydantic 模型:数据契约
Pydantic 模型是 FastAPI 类型安全的核心。用 BaseModel 定义请求体和响应体:
python
from pydantic import BaseModel, Field, EmailStr
from datetime import datetime
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=20)
email: EmailStr
age: int = Field(ge=0, le=150)
class UserOut(BaseModel):
id: int
username: str
email: EmailStr
created_at: datetime
model_config = {"from_attributes": True}
Field(...) 里的 ... 表示必填,min_length、ge(大于等于)、le(小于等于)都是校验规则。EmailStr 需要额外安装 pip install "pydantic[email]"。
from_attributes=True 允许你直接把 ORM 对象(比如 SQLAlchemy 的 User 实例)传给响应模型,FastAPI 会自动按字段名读取属性。
4. 常用 API:路径、查询、请求体与响应
路径参数与查询参数
python
from fastapi import FastAPI, Query, Path
app = FastAPI()
@app.get("/products/{product_id}")
def get_product(
product_id: int = Path(..., gt=0, description="商品ID,必须为正整数"),
page: int = Query(1, ge=1, description="页码"),
size: int = Query(10, ge=1, le=100, description="每页数量"),
):
return {"product_id": product_id, "page": page, "size": size}
路径参数用 Path 加约束,查询参数用 Query。description 会显示在文档里,前端一看就懂。访问 /products/3?page=2&size=20 正常返回;访问 /products/0 会得到 422,因为 gt=0 不满足。
请求体
python
from pydantic import BaseModel
class ProductCreate(BaseModel):
name: str
price: float
in_stock: bool = True
@app.post("/products", status_code=201)
def create_product(product: ProductCreate):
return {"id": 1, **product.model_dump()}
把 Pydantic 模型作为参数类型,FastAPI 自动从请求体解析 JSON 并校验。status_code=201 让创建接口返回正确的状态码。model_dump() 是 Pydantic v2 的方法,把模型转成字典(v1 是 .dict())。
响应模型
python
@app.post("/users", response_model=UserOut)
def create_user(user: UserCreate):
# 假设这里写入了数据库,返回 ORM 对象
return db_user
response_model 有两个作用:一是过滤掉模型里没声明的字段(比如密码哈希不会泄露),二是让文档准确展示响应结构。这是安全性的重要一环,别偷懒。
状态码与异常
python
from fastapi import HTTPException, status
@app.get("/users/{user_id}")
def get_user(user_id: int):
user = fake_db.get(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="用户不存在",
)
return user
HTTPException 会返回标准的 {"detail": "..."} JSON。用 status 常量比手写数字更可读。
5. 完整案例:一个可运行的任务管理 API
下面这个例子把前面所有知识点串起来,包含增删查改、数据校验、依赖注入和自动文档。
python
from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Annotated
app = FastAPI(title="任务管理 API", version="1.0.0")
# ---------- 数据模型 ----------
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
description: str | None = None
priority: int = Field(1, ge=1, le=5)
class TaskUpdate(BaseModel):
title: str | None = None
description: str | None = None
priority: int | None = Field(None, ge=1, le=5)
done: bool | None = None
class TaskOut(BaseModel):
id: int
title: str
description: str | None
priority: int
done: bool
created_at: datetime
# ---------- 模拟数据库 ----------
fake_tasks: dict[int, dict] = {}
_counter = {"next_id": 1}
# ---------- 依赖注入 ----------
def get_task_or_404(task_id: int) -> dict:
task = fake_tasks.get(task_id)
if not task:
raise HTTPException(404, detail=f"任务 {task_id} 不存在")
return task
TaskDep = Annotated[dict, Depends(get_task_or_404)]
# ---------- 接口 ----------
@app.post("/tasks", response_model=TaskOut, status_code=201, tags=["任务"])
def create_task(payload: TaskCreate):
task_id = _counter["next_id"]
_counter["next_id"] += 1
task = {
"id": task_id,
**payload.model_dump(),
"done": False,
"created_at": datetime.now(),
}
fake_tasks[task_id] = task
return task
@app.get("/tasks", response_model=list[TaskOut], tags=["任务"])
def list_tasks(done: bool | None = None):
tasks = list(fake_tasks.values())
if done is not None:
tasks = [t for t in tasks if t["done"] == done]
return tasks
@app.get("/tasks/{task_id}", response_model=TaskOut, tags=["任务"])
def get_task(task: TaskDep):
return task
@app.patch("/tasks/{task_id}", response_model=TaskOut, tags=["任务"])
def update_task(task: TaskDep, payload: TaskUpdate):
data = payload.model_dump(exclude_unset=True)
task.update(data)
return task
@app.delete("/tasks/{task_id}", status_code=204, tags=["任务"])
def delete_task(task: TaskDep):
fake_tasks.pop(task["id"])
return None
运行 uvicorn main:app --reload 后打开 http://127.0.0.1:8000/docs,你会看到五个接口按「任务」分组,每个都能在页面上直接点「Try it out」测试。
几个值得注意的细节:
model_dump(exclude_unset=True)只返回客户端真正传了的字段,这样 PATCH 不会把没传的字段覆盖成None。TaskDep = Annotated[dict, Depends(get_task_or_404)]把「取任务或抛 404」的逻辑抽成依赖,三个接口复用,不用重复写查询和判空。status_code=204的删除接口不返回内容,符合 REST 规范。
用 curl 测试创建:
bash
curl -X POST http://127.0.0.1:8000/tasks \
-H "Content-Type: application/json" \
-d '{"title":"写博客","priority":3}'
预期返回:
json
{"id":1,"title":"写博客","description":null,"priority":3,"done":false,"created_at":"2025-01-01T10:00:00"}
如果传 {"title":"","priority":9},会返回 422 并指出 title 太短、priority 超出范围。
6. 进阶技巧:异步、依赖注入与文档定制
异步请求处理
FastAPI 对 async def 和普通 def 都支持,但行为不同。async def 在事件循环里运行,适合 I/O 密集操作;普通 def 会被丢到线程池,适合阻塞式代码(比如同步的数据库驱动)。
python
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/weather/{city}")
async def get_weather(city: str):
async with httpx.AsyncClient() as client:
resp = await client.get(f"https://api.example.com/weather?city={city}")
return resp.json()
这里用 httpx.AsyncClient 发起异步 HTTP 请求,在等待外部接口响应时,事件循环可以去处理其他请求。如果你在 async def 里调用了阻塞函数(比如 requests.get 或 time.sleep),会卡住整个事件循环,性能反而比同步还差。记住一条原则:async 里只用异步库,同步库就写成普通 def。
依赖注入进阶
依赖可以嵌套,也可以带参数:
python
from fastapi import Depends, Header, HTTPException
async def verify_token(authorization: str = Header(...)):
if not authorization.startswith("Bearer "):
raise HTTPException(401, detail="无效的认证头")
token = authorization.removeprefix("Bearer ")
if token != "secret-token":
raise HTTPException(401, detail="token 无效")
return token
@app.get("/profile")
def profile(token: str = Depends(verify_token)):
return {"token": token}
Header(...) 自动把请求头 Authorization 映射到参数。依赖函数可以继续依赖别的依赖,形成一棵依赖树,FastAPI 会按需缓存和解析。这在做权限校验、数据库会话管理时特别省事。
自定义文档与响应示例
python
class Item(BaseModel):
name: str
price: float
model_config = {
"json_schema_extra": {
"examples": [{"name": "键盘", "price": 299.0}]
}
}
@app.post("/items", response_model=Item)
def create_item(item: Item):
return item
json_schema_extra 里的示例会显示在 Swagger UI 的请求体示例区,前端联调时不用猜字段格式。
后台任务
有些操作不需要让用户等,比如发邮件、写日志:
python
from fastapi import BackgroundTasks
def send_email(to: str):
print(f"发送邮件到 {to}")
@app.post("/register")
def register(email: str, background: BackgroundTasks):
background.add_task(send_email, email)
return {"msg": "注册成功,邮件稍后发送"}
响应立即返回,send_email 在响应发出后执行。
7. 真实工作场景:FastAPI 能解决什么问题
场景一:给前端提供数据接口。 前后端分离项目里,前端最怕字段类型和文档对不上。FastAPI 的 /docs 是实时从代码生成的,改了模型文档立刻更新,前端可以直接在页面上试接口、复制 curl 命令。response_model 保证返回结构稳定,不会因为数据库多了字段就泄露出去。
场景二:微服务之间的内部 API。 服务间调用对性能敏感。FastAPI 基于 ASGI,配合 uvicorn + uvloop,单机轻松支撑几千 QPS。异步处理让它在等待下游服务时不会阻塞。依赖注入系统天然适合做统一鉴权、请求 ID 透传、链路追踪。
场景三:机器学习模型推理服务。 数据科学家用 Pydantic 定义输入特征,FastAPI 自动校验类型和范围,模型只负责推理。异步接口还能做批量请求排队。很多 MLOps 工具(如 BentoML)底层就用了 FastAPI。
场景四:快速原型与内部工具。 一个下午写十几个接口,带上完整文档和校验,直接丢给同事用。相比 Django REST Framework,FastAPI 的样板代码少得多,学习曲线也平缓。
一个真实的生产注意点:数据库操作不要放在 async 路由里用同步驱动 。如果你用 SQLAlchemy 同步版本,路由就写 def;如果用 asyncpg 或 SQLAlchemy 2.0 异步版本,才写 async def。混用是新手最常见的性能陷阱。
8. 常见错误与避坑指南
错误一:响应模型字段缺失导致 500。 如果你声明了 response_model=UserOut,但返回的对象缺少 UserOut 里的必填字段,FastAPI 会抛 ResponseValidationError。解决方法是确保返回数据完整,或者把字段设为可选。
错误二:混淆 Pydantic v1 和 v2 的 API。 v2 里 .dict() 改成了 .model_dump(),.parse_obj() 改成了 .model_validate(),Config 类改成了 model_config 字典。网上很多教程还是 v1 写法,照抄会报错。装库时确认版本:pip show pydantic。
错误三:在 async 函数里用阻塞调用。 前面强调过,requests、time.sleep、同步数据库查询都会阻塞事件循环。要么换成异步库,要么把路由改成普通 def。
错误四:路径参数顺序冲突。 如果你同时有 /users/me 和 /users/{user_id},/users/me 必须声明在前面,否则 me 会被当成 user_id 去匹配,然后因为类型转换失败返回 422。
错误五:CORS 没配置导致前端请求被拦。 前后端分离时,浏览器会拦截跨域请求。加上中间件:
python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["*"],
allow_headers=["*"],
)
生产环境把 allow_origins 写成具体域名,别用 ["*"]。
错误六:把 --reload 带到生产。 它监听文件变化,会持续消耗资源,且重启会中断请求。生产用 uvicorn main:app --workers 4 配合进程管理器。
9. 总结
FastAPI 的核心价值可以浓缩成一句话:用类型注解描述接口,剩下的交给框架 。你声明参数类型,它做校验;你声明响应模型,它做过滤和文档;你写 async def,它给你异步性能;你用 Depends,它帮你复用逻辑。
回顾一下关键点:
- 安装用
pip install "fastapi[standard]",启动用uvicorn main:app。 - 核心对象是
FastAPI实例、APIRouter分组、BaseModel数据契约。 - 常用 API 包括
Path、Query、Header、Depends、HTTPException、response_model。 - 异步路由里只放异步调用,同步代码就写普通
def。 - 依赖注入是复用鉴权、数据库会话、资源加载的利器。
/docs是自动生成且实时更新的,别再去手写接口文档。
如果你想开始,建议从第 5 节那个任务管理案例动手,跑起来,然后打开 /docs 点一遍每个接口。改改字段类型,看看校验报错长什么样。半小时后,你对 FastAPI 的直觉就建立起来了。