第 1 章:初识 FastAPI
学框架最怕「上来就抄代码,抄完不知道怎么坏」。本章先不写复杂业务,把 FastAPI 的底座、能力边界和适用场景讲清楚——知道它为什么快、快在哪,后面 18 章才不容易走弯路。
学习目标
- 理解 WSGI 与 ASGI 的差异,能说清同步模型在高并发 I/O 场景下到底卡在哪
- 掌握 FastAPI 的技术底座:Starlette 负责 Web 层、Pydantic 负责数据层,FastAPI 本身是二者的粘合与增强
- 能逐条列出 FastAPI 的五项核心特性,并解释每一项解决什么问题
- 能独立跑通最小应用,并说清
/docs、/redoc、/openapi.json三者的关系 - 能对比 FastAPI / Flask / Django + DRF,判断自己的项目该不该选 FastAPI
1.1 从 WSGI 到 ASGI
Python Web 框架长期建立在 WSGI(Web Server Gateway Interface,PEP 3333)之上:服务器把请求打包成 environ 字典交给应用,应用返回一个可迭代的字节序列。这个接口是同步的——应用函数必须在这一个调用里把响应算完。
同步模型的问题不在于「慢」,而在于等待期间线程被白白占住:
- 查一次数据库 20 ms、调一次外部 API 200 ms、读一次对象存储 30 ms,这些时间 CPU 什么都没干,但处理请求的线程必须原地阻塞
- 想提高并发只能加线程或加进程;线程受 GIL 限制、上下文切换有开销,进程则吃内存
- 结果就是:I/O 越重,单机性价比越低,1 个 worker 撑住 10 个并发请求就要烧掉 10 个线程
ASGI(Asynchronous Server Gateway Interface)把接口改成基于协程的:应用是一个 async 可调用对象,接收 scope、receive、send 三个参数,可以反复 await。这带来两个关键能力:
| 能力 | 说明 |
|---|---|
| 异步 I/O 并发 | 单线程事件循环可以在等待 I/O 时切换去处理其他请求,1 个 worker 轻松撑住成百上千个并发连接 |
| 协议可扩展 | scope["type"] 可以是 http、websocket、lifespan,因此 WebSocket、HTTP/2、长连接天然可支持 |
⚠️ 关键认知:
async不是「加速器」。它只对 I/O 密集 场景有效;对 CPU 密集计算(图像处理、加解密、复杂算法)没有任何帮助,反而因为单线程事件循环容易被拖垮。选型时先判断你的瓶颈是 I/O 还是 CPU。
1.2 FastAPI 的技术底座
FastAPI 不是从零造轮子,这一点决定了它的稳定性和学习成本。它的依赖只有四个(见官方 pyproject.toml):starlette、pydantic、typing-extensions、typing-inspection。
FastAPI = Starlette(Web 层) + Pydantic(数据层) + FastAPI 自己的三样东西
路由 / 中间件 / 校验 / 序列化 / 1. 类型提示 → 参数的映射规则
WebSocket / 生命周期 JSON Schema 生成 2. 依赖注入系统(Depends)
3. OpenAPI 文档自动生成- Starlette:一个轻量 ASGI 框架,提供路由、中间件、请求/响应对象、WebSocket、后台任务、
lifespan。FastAPI 的app本质上就是一个增强了路由与文档能力的 Starlette 应用。 - Pydantic v2:Python 里事实标准的数据校验与序列化库,核心由 Rust 实现(
pydantic-core),所以校验速度比 v1 有量级提升。
理解了这层关系,很多疑问会自动消失:为什么中间件写法和 Starlette 一样?为什么 Request、Response 从 fastapi 导入但其实是 Starlette 的?为什么校验错误是 Pydantic 的格式?
1.3 五项核心特性
1.3.1 类型提示驱动的参数声明
FastAPI 把「函数签名」当作唯一事实来源。你不需要写 @app.route 之外的额外声明,参数的来源与类型全靠注解推导:
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
...- 名字出现在路径模板里 → 路径参数
- 名字不在路径模板里、且是简单类型 → 查询参数
- 是 Pydantic 模型 → 请求体
1.3.2 自动数据校验与序列化
item_id: int 不只是给人看的注解。FastAPI 会用 Pydantic 把字符串 "42" 转成整数 42;转不了就返回 422 Unprocessable Entity,并给出字段级错误详情。返回值则被自动序列化为 JSON。
1.3.3 原生 async
路径操作函数既可以是 async def,也可以是普通 def。区别很重要:
async def:在事件循环里直接await,适合异步 I/O(如AsyncSession、httpx.AsyncClient)def:FastAPI 会把它丢到 线程池 执行,不阻塞事件循环,适合调用同步库
1.3.4 自动生成 OpenAPI 文档
启动即拥有 /openapi.json、/docs、/redoc,全部由代码与 Pydantic 模型推导,无需维护独立的接口文档文件。
1.3.5 依赖注入
Depends 让「共用逻辑」(数据库会话、当前用户、分页参数、权限校验)可以声明式地注入,并且天然支持嵌套、缓存与测试覆盖。这是 FastAPI 区别于其他微框架最有价值的一块,第 6 章会专门展开。
1.4 最小可运行示例
本章先用单文件 main.py,第 11 章会重构为 app/ 分层结构。
# main.py
from fastapi import FastAPI
app = FastAPI(title="FastAPI 教程 Demo", version="0.1.0")
@app.get("/")
async def read_root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}逐行解释 FastAPI 的「读心术」:
| 代码 | FastAPI 的解释 |
|---|---|
app = FastAPI() | 创建一个 ASGI 应用实例,同时建立内部路由表与 OpenAPI 元数据 |
@app.get("/") | 注册一个 路径操作(Path Operation):路径 / + HTTP 方法 GET |
item_id 出现在 {item_id} 中 | 声明为路径参数,位置由 URL 模板决定 |
item_id: int | 声明类型为 int,运行时自动转换与校验,失败返回 422 |
q: str | None = None | 不在路径模板中 → 查询参数;有默认值 → 可选 |
return {...} | 字典被 Pydantic 序列化为 JSON 响应,默认状态码 200 |
启动与验证:
uv run fastapi dev main.py
# 或 pip 环境:fastapi dev main.py
curl "http://127.0.0.1:8000/items/42?q=hello"
# {"item_id":42,"q":"hello"}
curl "http://127.0.0.1:8000/items/abc"
# 422,detail 里说明 int_parsing 失败1.5 自动文档三件套
| 端点 | 是什么 | 什么时候用 |
|---|---|---|
/openapi.json | 机器可读的 OpenAPI 3.1 规范(JSON) | 生成客户端 SDK、导入 Postman / Apifox、CI 里做契约校验 |
/docs | Swagger UI,可交互 | 开发调试首选,能直接填参数发请求 |
/redoc | ReDoc,只读、三栏布局 | 接口多的时候通读更舒服,适合交付给前端与外部对接方 |
三者的关系是一份规范、两种渲染:/docs 和 /redoc 都从 /openapi.json 拉数据。所以改代码就是改文档,不存在文档过期问题——这也是 FastAPI 最省事的一点。
1.6 与 Flask / Django + DRF 横向对比
| 维度 | FastAPI | Flask | Django + DRF |
|---|---|---|---|
| 异步支持 | 原生 ASGI,async/await 是一等公民 | 传统 WSGI 同步;2.x 起支持 async 视图,但周边生态多为同步 | Django 3.1+ 支持异步视图,ORM 异步能力在 4.1+ 逐步补齐,整体仍以同步为主 |
| 数据校验 | Pydantic 内建,与类型注解同源 | 需自行引入 marshmallow / pydantic / WTForms | DRF Serializer,成熟稳定但字段需手写 |
| 自动文档 | 默认生成 OpenAPI + Swagger UI / ReDoc | 需扩展(APIFlask、flask-smorest) | 需 drf-spectacular / drf-yasg |
| ORM | 不自带,自行选 SQLAlchemy / SQLModel / Tortoise | 不自带,自行搭配 | 自带 Django ORM + 迁移,一体化 |
| 学习曲线 | 低到中:需懂类型注解,异步要理解事件循环 | 低:最小的心智负担 | 高:约定多、概念多,但「开箱即用」程度最高 |
| 适合场景 | API 优先、I/O 密集、微服务、AI/LLM 服务端 | 小工具、原型验证、以模板渲染为主的小站 | 后台管理重的内容型站点、需要全家桶的企业内部系统 |
取舍要点(不吹不黑):
- FastAPI 赢在 API 场景:校验、文档、依赖注入、异步四件事组合起来,写接口的样板代码最少。
- Flask 赢在 简单:几十行的工具,Flask 仍然更直接,不必为类型注解和异步付出认知成本。
- Django 赢在 一体化:
admin、ORM、迁移、认证、模板、表单全都自带。一个内容后台项目,Django admin 能省掉几周的开发量,FastAPI 要从零搭。
1.7 什么时候该选 FastAPI
适合:
- 前后端分离的纯 API 后端,或给移动端 / 小程序提供接口
- 需要自动文档与客户端 SDK,前后端协作频繁
- I/O 密集:大量调用外部 API、数据库、对象存储、LLM 服务
- 微服务中的单个服务,要求镜像小、启动快
- 已有 Python 数据/算法代码(pandas、PyTorch),要包一层 HTTP 服务
不适合:
- 以服务端模板渲染 + 后台管理为主的传统内容站:Django 的 admin 与 ORM 是更好的选择
- CPU 密集是主瓶颈:换框架不解决问题,需要的是多进程、Celery 或原生扩展
- 团队完全不熟悉类型注解,项目又非常简单:学习成本未必划算
- 需要开箱即用的完整后台、权限、审计体系:Django 生态现成轮子更多
1.8 错误示范 vs 正确示范
❌ 不推荐:以为 async def 就等于快。
import time
import requests
from fastapi import FastAPI
app = FastAPI()
@app.get("/sync-in-async")
async def sync_in_async():
time.sleep(1) # 阻塞事件循环 1 秒
resp = requests.get("https://example.com") # 同步 HTTP,同样阻塞
return {"length": len(resp.text)}问题:async def 里调用阻塞函数,会卡死整个事件循环——这一个请求等待期间,服务器上所有其他请求都无法被处理,比同步框架更糟。
✅ 推荐:异步路由配异步库;同步库用普通 def 交给线程池。
import asyncio
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/async-ok")
async def async_ok():
await asyncio.sleep(1) # 让出事件循环
async with httpx.AsyncClient() as client:
resp = await client.get("https://example.com")
return {"length": len(resp.text)}
@app.get("/use-threadpool")
def use_threadpool():
import time
time.sleep(1) # 普通 def,FastAPI 丢进线程池执行
return {"ok": True}常见坑与排查
| 现象 | 原因 | 解决 |
|---|---|---|
找不到 from fastapi.orm import ... 之类的模块 | 以为 FastAPI 自带 ORM | FastAPI 不含 ORM,需自行安装 SQLAlchemy / SQLModel,见第 10 章 |
| 并发压测时 QPS 很低,日志显示请求排队 | 在 async def 里写了阻塞调用(requests、time.sleep、同步 DB 驱动) | 换成 httpx.AsyncClient、asyncio.sleep、异步驱动;或把该路由改成普通 def |
照着搜索引擎的代码写 @validator、.dict() 报错 | 搜到的是 Pydantic v1 的旧文档 | 本项目统一 Pydantic v2:@field_validator、model_dump()、model_config = ConfigDict(...) |
| 端口 8000 上打开了别的项目 | 已有进程占用,FastAPI 启动时会报 address already in use | fastapi dev main.py --port 8001,或先结束占用进程 |
访问 /docs 是 404 | 应用实例被创建时传了 docs_url=None,或访问的是错误的前缀 | 检查 FastAPI(...) 参数;带 root_path 部署时确认网关前缀 |
| 以为 FastAPI 能直接替代 Django | 缺少 admin、ORM、模板优先等一体化能力 | 明确项目性质;需要重后台时选 Django,或为 FastAPI 自建管理端 |
类型注解写了 Optional[str] 也能跑 | 语法可用,但不符合本项目规范 | 统一写 str | None(Python 3.10+ 起原生支持) |
本章小结
| 要点 | 说明 |
|---|---|
| WSGI 的瓶颈 | 同步接口在等待 I/O 时占住线程,I/O 越重并发性价比越低 |
| ASGI 的收益 | 协程化接口,单 worker 可承载大量并发连接,并原生支持 WebSocket |
| 技术底座 | Starlette 提供 Web 能力,Pydantic v2 提供校验与序列化,FastAPI 负责粘合与增强 |
| 五大特性 | 类型提示驱动声明、自动校验序列化、原生 async、自动 OpenAPI、依赖注入 |
| 参数来源推导 | 在路径模板中 → 路径参数;简单类型且不在模板中 → 查询参数;Pydantic 模型 → 请求体 |
| 文档三件套 | 一份 /openapi.json,/docs 与 /redoc 两种渲染,改代码即改文档 |
| 选型结论 | API 优先、I/O 密集、需要自动文档 → FastAPI;重后台与一体化 → Django;极简小站 → Flask |
| 核心警告 | async def 不等于快,阻塞调用会拖垮事件循环 |
练习题
- 运行本章的最小示例,用
curl分别请求/items/42、/items/42?q=abc、/items/abc,记录三次响应的状态码与 body,并解释 422 响应中detail[0]["type"]的含义。 - 打开
/docs,用 Swagger UI 的 Try it out 向/items/{item_id}发一次请求,然后打开/openapi.json,找到这个路径对应的 JSON 片段,对照说明 Swagger UI 的表单字段是从哪里来的。 - 写一个路由
/slow-async,在async def中调用time.sleep(2);再用浏览器同时打开两个标签页请求它,观察第二个请求是否被阻塞。然后把函数改成普通def再测一次,解释差异。 - 结合 1.6 与 1.7 的表格,为一个你熟悉的真实项目做选型:写出项目的 I/O 特征、是否需要后台管理、团队技术栈,并给出选 FastAPI 或 Django 的三条理由。
下一章预告
原理讲完了,接下来把环境落地:用
uv或venv装好依赖,跑通热重载开发服务器,并在 Swagger UI 里亲手发出第一个请求。