FastAPI 是一个基于 Python 类型提示的现代 Web 框架:路由参数解析、请求数据校验、序列化输出、交互式文档,全部由函数签名上的类型标注自动驱动。它由 Starlette 提供 ASGI 内核、Pydantic 提供数据验证引擎,这两层组合决定了它的性能特征与开发体验。
阅读完本节,你应当能够:
理论不如代码直观。假设要实现一个"查询商品详情"的接口:路径里带商品 id,必须转成整数,不合法要返回 422 错误。先看 Flask 的典型写法:
from flask import Flask, jsonify, abort app = Flask(__name__) @app.route("/items/<item_id>") def get_item(item_id): if not item_id.isdigit(): abort(404) item_id = int(item_id) item = find_item_or_none(item_id) if item is None: abort(404) return jsonify(id=item.id, name=item.name, price=item.price)
再看 FastAPI 版本:
from fastapi import FastAPI, HTTPException app = FastAPI() @app.get("/items/{item_id}") def get_item(item_id: int): item = find_item_or_none(item_id) if item is None: raise HTTPException(status_code=404, detail="item not found") return {"id": item.id, "name": item.name, "price": item.price}
差异不在行数,而在职责的归属。Flask 版里,"参数必须是整数、不合法怎么办"这件事由你的代码负责;FastAPI 版里,item_id: int 这一个标注把类型转换、非法值拦截、422 错误响应全部接管了。传一个 /items/abc 过去,前者会抛 404(因为你手动判断了),后者直接返回一个结构化的 422 响应,body 里明确写着哪个参数、什么值、什么错。
这就是 FastAPI 的核心主张:接口的契约不该活在注释和文档里,应该活在函数签名里,让机器去执行这份契约。
FastAPI 不是一个从零造的框架,它是三层已有技术的组合者,理解这个分层比背特性重要。
最底层是 ASGI 服务器(开发时通常用 uvicorn)。传统 Python Web 框架跑在 WSGI 协议上——那是一个"一个请求进来,同步处理完再返回"的模型,协议本身没有异步的概念。ASGI 在协议层面支持 async 调用,一个进程内可以同时挂起成百上千个等待中的请求。这层决定了 FastAPI 的性能天花板,第 3 章会详细剖析。
中间层是 Starlette。它是一个轻量 ASGI 框架,提供了路由、中间件、请求响应对象、TestClient 这些 Web 框架的骨架。你可以把 FastAPI 理解为"Starlette 加上了类型驱动的 API 层"——FastAPI 的 Request、Response、中间件写法直接继承自 Starlette,很多行为查 Starlette 文档反而更清楚。
最上面是 Pydantic,负责数据建模与验证。所有请求体、响应模型、配置项,本质都是 Pydantic 模型。第 2 章会用整章展开它,包括 v1 与 v2 的关键差异。
这个分层带来一个实用推论:FastAPI 的性能基本等于 Starlette 的性能(验证层有一点开销),社区基准里它常与 Starlette 并列第一梯队,明显快于 Flask 与 Django。但快的主要功劳不在 FastAPI 自己的代码,而在架构选择——异步协议加上更少的抽象层。

Python 3.5 引入类型提示时,它只是给人和静态检查工具看的注释,解释器运行时并不 enforcing。PEP 484 之后生态逐渐形成一个思路:框架在运行时读取这些标注,把它们变成行为。
FastAPI 把这个思路推到了极致。函数签名里的每个元素都有语义:
from typing import Annotated from fastapi import Query @app.get("/search") def search( q: str, # 查询参数,必填 page: int = 1, # 查询参数,默认值 size: int = Query(10, ge=1, le=100), # 查询参数,带范围约束 ): ...
运行时 FastAPI 会用反射读取 search 的签名,发现 q 没有默认值就要求必填,发现 size 带 Query 约束就校验 1 到 100 的范围。这一切不需要你写任何 if 判断。
对比一下"没有这套机制"的世界:Flask 里这些校验散落在视图函数开头,Express 里靠手写中间件或 Joi 手动调用,Django REST framework 有 serializer 但要单独定义一个类再显式调用 is_valid。FastAPI 的差异在于校验逻辑与接口定义合并在同一处,改签名就是改契约,不存在"文档说 int、代码里却是 str"的漂移。
因为契约存在于签名里,FastAPI 能顺手把它导出为 OpenAPI 规范的 JSON,并在 /docs 挂一个交互式界面(Swagger UI),在 /redoc 挂一个阅读友好版。你可以直接在文档页面上填参数、发请求、看真实响应。
这件事的分量要放在协作语境里看。传统流程是:后端写完接口 → 手写接口文档 → 前端照文档对接 → 后端改了代码忘了改文档 → 前端联调暴雷。FastAPI 把中间那步"手写文档"消灭了,文档永远和代码同步,因为它就是从代码生成的。
不过也要说清楚边界:自动文档只覆盖"签名里声明的东西"。业务含义(这个字段为什么只能取这几个值、什么场景下会返回什么错误码)仍然需要人写在 docstring 或字段描述里,自动生成的只是骨架。
理解一个框架的定位,看它出现的时间点往往比看特性表更有信息量。Flask 诞生于 2010 年前后,那时 Python 社区的主流诉求是"比 Django 更轻地建站",同步 WSGI 是理所当然的底座。Starlette 出现在 2018 年, asyncio 已经成熟,社区需要一个原生异步的 Web 工具集,但 Starlette 刻意停留在"工具集"层面,不替你做数据校验,也不管文档。FastAPI 在 2018 年底发布,作者 Sebastián Ramírez 的切入点不是"再造一个框架",而是把三个已经各自成熟的部件——asyncio 生态、Starlette 的 ASGI 骨架、Pydantic 的运行时类型验证——用类型提示这条线缝成一体。
值得,但只剩一半价值。去掉标注后你仍有异步并发、仍有依赖注入与中间件,但校验回到手写、文档退化为空壳——框架最独特的部分恰恰是最依赖类型纪律的部分。所以更准确的说法是:FastAPI 是把"Python 类型提示的运行时价值"兑换成生产力的兑换机,你投入多少纪律,它支付多少回报。这也是为什么团队引入 FastAPI 之前,先统一类型标注规范比先学框架 API 更重要。
这个缝合格局解释了两件常被问到的事。其一,为什么 FastAPI 的版本号长期停在 0.x 却被大量生产系统采用——因为它的核心依赖都是久经考验的独立项目,FastAPI 本体主要是胶水层,本体变动风险相对可控。其二,为什么它对 Pydantic v1 到 v2 的迁移如此敏感——验证引擎不是它自己的代码,大版本切换时校验器写法、错误行为都会跟着变,这也是第 2 章要专门对比 v1 与 v2 的原因。
作为对照,Django 与 Flask 的异步化是"在既有同步骨架上加装异步支持",要照顾十年以上的存量生态;FastAPI 是"在异步地基上盖房子",没有历史包袱。两种路径没有绝对优劣,但解释了为什么今天谈高并发 Python API 时,FastAPI 会成为默认候选。
初学者往往在第一次传错参数时真正理解 FastAPI。试着给前面的接口发一个 GET /items/abc,得到的不是一行模糊的 500,而是一个结构化的 422 响应:
{ "detail": [ { "type": "int_parsing", "loc": ["path", "item_id"], "msg": "Input should be a valid integer, unable to parse string as an integer", "input": "abc" } ] }
四段信息各有用途:loc 告诉你错在路径参数还是请求体的哪个字段,type 是机器可读的错误码,msg 给人看,input 回显原始值。Flask 的手写校验要做到这一点,得自己设计一套错误结构并坚持在每个视图里使用;FastAPI 里它是统一且免费的。前端与测试脚本可以只针对 detail 结构写一次通用处理逻辑,覆盖全站所有接口——接口越多,这笔一致性收益越大。
这个设计还有一层对比价值:它把"调试信息"变成了"对接契约"。前端联调时不必问后端"这个参数格式错了返回什么",直接看文档里的错误示例;自动化测试断言 422 时可以直接匹配 type 字段,不依赖脆弱的错误文案。传统框架里错误格式的随意性是联调摩擦的主要来源之一,统一错误结构等于把这类摩擦一次性清零。
避免神化。FastAPI 不是全家桶:没有 ORM、没有 Admin 后台、没有用户系统、没有迁移工具。Django 这些全给你,FastAPI 一概不管,你需要自己选 SQLAlchemy 或 Tortoise,选 Alembic,自己搭后台。这个"少"在第 1.2 节的对比里既是它的灵活性来源,也是小团队的成本来源。
它也不适合纯同步重计算的场景——CPU 密集型任务放在 async 路由里会阻塞整个事件循环,反而更糟(第 3 章展开)。另外,团队若没有类型提示习惯,强上 FastAPI 会写出大量没有标注的代码,等于放弃了它的全部优势,只剩一个普通异步框架。
名字容易让人以为它做不了别的。实际上基于 Starlette 的能力,它可以返回 HTML、挂载静态文件、渲染 Jinja2 模板、处理 WebSocket 与 SSE,写一个传统多页网站没有障碍。只是这些场景下它的类型驱动优势用不上多少,而 Flask 与 Django 在模板渲染、表单处理上的顺手程度更高。所以更准确的定位说法是:它是一个能力完整的 Web 框架,但只在 API 场景下把优势拉满。第 4 章响应处理一节会把这些非 JSON 输出方式过一遍。
不用,但值得在入门后回头补。日常开发 90% 的问题查 FastAPI 文档就能解决;剩下 10% 的疑难——中间件的执行顺序、Request 对象的生命周期、后台任务与响应返回的时序——答案往往在 Starlette 的文档里。Pydantic 同理,第 2 章覆盖了它的日常用法,深挖校验器与序列化配置时再去看它的文档效率更高。先框架后底座的顺序,比一开始就啃三个项目文档的挫败感小得多。
版本号是个营销问题而不是工程问题。FastAPI 的核心路径(路由、验证、依赖注入)多年保持稳定,破坏性变更集中在边角行为且有迁移说明。比起版本号,更值得关注的是它对 Pydantic 大版本的跟随策略——这才是实际项目里升级成本的主要来源。
下一节我们把 Flask、Django、Express 请上桌,做一次五个维度的正面对比,回答那个绕不开的问题:什么约束下该选 FastAPI。