1.2 为什么选择FastAPI:四框架横向对比


1.2 为什么选择 FastAPI:与 Flask Django Express 横向对比

选型结论先行:IO 密集的 API 服务(尤其是要扛高并发读、对接异步数据库与异步第三方调用的场景)首选 FastAPI;重内容管理、需要 Admin 与完整脚手架的团队仍应选 Django;小工具与原型 Flask 依旧够用;前后端同栈的团队 Express 依然是自然选择。本节用五个维度把这个结论的推理过程摆出来。

本节能力目标

阅读完本节,你应当能够:

  1. 在路由、校验、文档、异步、生态五个维度上对比四个框架;
  2. 识别自己项目属于哪类约束,并对应到框架选择;
  3. 说出"FastAPI 性能好"的准确原因,避免归因错误;
  4. 给出至少三个不建议用 FastAPI 的场景。

一、对比之前:先明确评估框架的角度

框架对比最容易变成信仰之争,原因往往是评估维度不清。我们固定五个维度,每个维度都给两边的代码事实,不谈感受:

  1. 路由与参数处理:接口怎么声明,参数怎么进来;
  2. 数据校验:非法数据在哪里被拦住,谁来写这段逻辑;
  3. 文档生成:接口文档的维护成本;
  4. 异步与并发模型:高 IO 并发下的行为;
  5. 生态与学习曲线:招人、周边、踩坑资料的存量。

二、维度一:路由与参数处理

四个框架都用装饰器或链式调用注册路由,表面相似,参数语义差别很大。

Flask 把路径参数当字符串交给你,类型转换自己写;查询参数用 request.args.get 手取。Express 用 :item_id 占位,参数在 req.params 里,同样是无类型字符串,校验靠手写或 Joi 中间件。Django REST framework 用显式的视图类加解析器,路径参数在 URL 配置里用正则或路径转换器声明类型——<int:pk> 是 Django 里少数接近声明式类型的机制。

FastAPI 的差异在前面演示过:类型写在签名里,转换与拦截自动完成。再加一个对照——同一个"分页查询"接口的参数声明:

# Flask:类型、默认值、范围校验全手写 page = request.args.get("page", 1, type=int) size = request.args.get("size", 10, type=int) if size < 1 or size > 100: return jsonify(error="size out of range"), 400
# FastAPI:签名即全部 def list_items(page: int = 1, size: int = Query(10, ge=1, le=100)): ...

注意 Flask 版的 type=int 在传入 ?page=abc 时会静默回落到默认值 1——这是一个真实的坑:用户传错参数却拿到了第一页数据,错误被吞掉了。FastAPI 版会直接返回 422 并指明 page 字段非法。静默容错与显式报错,是两种工程哲学,API 对外服务时后者几乎总是更安全。

三、维度二:数据校验

请求体校验是差异最大的地方。Flask 拿到 JSON 后是裸 dict,字段存在性、类型、格式全靠自己 if 判断,或引入 marshmallow 另写一套 Schema。Express 生态常用 Joi 或 zod,同样是显式调用 validate。DRF 的 serializer 最接近:定义一个 Serializer 类,调 is_valid() 后取 validated_data

FastAPI 直接把校验绑在参数上:

from pydantic import BaseModel, Field, EmailStr class CreateUser(BaseModel): name: str = Field(min_length=1, max_length=50) email: EmailStr age: int = Field(ge=0, le=150) @app.post("/users") def create_user(payload: CreateUser): ... # 进到这里时数据一定合法

业务函数从第一行起就操作合法的强类型对象。对比 DRF:serializer 能力相当,但需要单独的类、显式的 is_valid() 调用,而且 serializer 与 ORM 模型的关系(ModelSerializer、嵌套写法)有自己的学习成本。对比 Flask + marshmallow:能力也够,但两套体系(视图 + Schema)之间的粘合代码全是你写。

结论:校验能力的上限四个生态都够(都接得住复杂规则),差别在"校验与接口定义的耦合度"——FastAPI 把两者压缩到一处,漂移空间最小。

四、维度三:文档生成

Flask 生态有 flask-smorest、apispec,能生成 OpenAPI,但需要显式装饰与维护。Express 有 swagger-jsdoc,靠注释块生成,注释与代码的同步全凭自觉。DRF 内置 OpenAPI 生成(新版本用 drf-spectacular 增强),是四者里唯一"开箱即用程度"接近 FastAPI 的。

FastAPI 的优势不在"能生成",而在生成物与运行行为的同源:文档描述的约束(类型、范围、必填)就是运行时真正执行的校验规则,因为两者读的是同一份签名。这一点 drf-spectacular 通过大量推断也能做到大部分,但边界场景(自定义校验器如何体现在 schema)仍需手动提示。

五、维度四:异步与并发模型

这是 FastAPI 的主场,也是误解最多的一维。先给一个可直接运行的对照实验,感受两种执行模型的差别(细节机制第 3 章拆解):

import asyncio import time from fastapi import FastAPI app = FastAPI() @app.get("/sync-wait") def sync_wait(): time.sleep(1) # 同步睡 1 秒 return {"model": "sync"} @app.get("/async-wait") async def async_wait(): await asyncio.sleep(1) # 异步让出 1 秒 return {"model": "async"}

用并发工具同时打五十个请求:两个接口都能响应,但如果把同步版放进 async 路由(去掉 def 改成 async def 且不改 sleep),事件循环被逐个卡住,五十个请求要排队近五十秒。**同样的等待时长,执行模型不同,吞吐天差地别。**这正是"async def 不等于更快"的直观教材:关键不在关键字,在于等待时是否让出控制权。

这是 FastAPI 的主场,也是误解最多的一维。

再横向看四个框架的底座差异。
Flask 直到 2.x 才支持 async 视图,但默认栈仍是 WSGI 同步模型,async 视图在缺少事件循环支撑的部署下并发收益有限,社区共识仍是"Flask 不为异步而生"。Django 自 3.1 引入 async 视图、4.x 逐步异步化 ORM,方向明确但生态(中间件、第三方包)的异步适配仍在进行中。Express 天生单线程事件循环,异步是它的母语,但 CPU 密集任务会卡死整个进程,需要 worker_threads 或拆服务。

FastAPI 原生 ASGI:async 路由跑在事件循环上,等待数据库或外部 API 时让出控制权,单进程可挂起大量并发请求。但必须强调反面:如果你在 async 路由里用了同步阻塞的数据库驱动(比如普通 psycopg2),每个请求都会卡住事件循环,吞吐比同步栈更差。异步是收益还是灾难,取决于整条调用链是否异步——这个话题第 3 章用整章展开。

给一个数量级感受(具体数值依赖场景,仅示方向):同样单进程等待外部 100ms 的 API,同步模型每秒约处理几十请求,事件循环模型可同时挂起上千个等待,吞吐提升一个数量级以上。前提是"等待 IO"而非"计算"。

六、五维总表

维度 FastAPI Flask Django/DRF Express
参数类型转换 签名自动 手写 路径转换器部分支持 手写或 Joi
请求体校验 Pydantic 绑定参数 自理或 marshmallow serializer 显式调用 Joi/zod 显式调用
自动文档 原生,与校验同源 需扩展 drf-spectacular 较完善 注释生成
异步并发 原生 ASGI 补充支持 演进中 原生事件循环
全家桶程度 仅 API 层 微框架 ORM Admin 迁移齐备 微框架
典型代价 类型纪律要求 校验文档自理 重量、学习曲线 回调心智、类型靠 TS 补

图示:选型决策路径

图示:选型决策路径

七、迁移成本:从 Flask 过来要改多少

选型讨论常常忽略迁移成本这一维度。从 Flask 迁到 FastAPI,概念映射其实是平滑的:app.route 换成带 HTTP 动词的装饰器、request.get_json 换成 Pydantic 模型参数、jsonify 换成直接返回 dict、before_request 钩子换成本章后面会讲的依赖注入或中间件。一个中等规模的服务(三四十个接口),熟悉 Flask 的工程师通常一两周可以完成主体迁移,因为业务逻辑层几乎不动,改的只是"边界层"——参数进来的方式和响应出去的方式。

真正的迁移风险不在语法而在两处。一是隐性行为差异:Flask 习惯了 type=int 静默回落、习惯了字典键不存在返回 None 再判空,这些"宽容"在 FastAPI 里都变成了 422 拒绝,如果上游真的在传脏数据,迁移当天就会暴露出来——这不是退步,而是把技术债一次性结清,但要预留出修数据的缓冲期。二是同步依赖的适配:如果旧服务里的 ORM、缓存客户端、消息队列 SDK 都是同步的,直接搬进 async 路由会把事件循环卡死,要么先用同步路由(FastAPI 会把 def 路由放进线程池,行为不劣于 Flask),要么分批替换为异步客户端。第 3 章会给出这条迁移路线的完整对照。

从 Django 迁移的情况较少见,因为两者定位重叠小:通常不是"迁移",而是新业务用 FastAPI 起独立服务,Django 继续管内容与后台,两者通过内部 HTTP 或消息队列协作。这种渐进式共存反而是微服务语境下最常见的落地形态。

八、社区与生态的实况

生态维度需要说些实话。Flask 与 Django 的资料存量是十几年积累,中文社区的问答密度至今仍高于 FastAPI,遇到偏门问题搜到的答案更多。FastAPI 的优势领域集中在近几年新增的实践:异步数据库客户端、大模型推理服务封装、Serverless 部署、OpenAPI 工具链集成——这些场景下新教程与新组件反而以 FastAPI 为默认前提。招人角度,Python 工程师学 FastAPI 的曲线平缓(核心概念一周内可上手),而"会 FastAPI"在很大程度上等价于"会写带类型提示的现代 Python",这个技能本身在团队内有溢出价值。

组件生态方面要认清一个事实:FastAPI 官方仓库刻意保持小而精,登录认证、数据库会话、限流这些在别的框架里由官方或半官方扩展承担的东西,这里由 fastapi-userssqlmodel 等独立项目或自己用依赖注入组合。依赖注入系统就是为此设计的——它把"粘合外部组件"这件事变成了框架的一等能力。接受这个设定后你会发现,FastAPI 项目里几乎没有"插件"概念,所有集成都用同一套依赖语法表达,心智反而更统一。

九、不建议选 FastAPI 的三种情况

第一,重内容管理的站点。新闻站、电商后台、需要 Admin 界面和权限管理后台的项目,Django 一套脚手架顶 FastAPI 手拼两周。第二,CPU 密集为主的服务。图像处理、报表计算这类负载,异步帮不上忙,Python 的 GIL 才是瓶颈,选什么 Web 框架都一样,反而应该考虑把计算拆到独立 worker。第三,团队完全没有类型提示习惯。FastAPI 的红利全部建立在签名标注之上,没有标注的 FastAPI 代码只是一个需要自己写校验的普通框架,此时 Flask 的资料存量反而更友好。

补一句容易被忽略的第四种情况:强依赖特定同步生态的项目。如果团队的核心资产是一套深度绑定 WSGI 的中间件、认证体系或监控探针,迁移到 ASGI 意味着整条链路重写,此时框架层面的性能收益未必抵得过改造成本。技术选型的理性姿态是算总账,不是单点比参数。

本节要点回顾

  • 五维对比:路由、校验、文档、异步、生态——FastAPI 在前四维的共性是"声明式、与校验同源",代价是放弃全家桶与要求类型纪律。
  • 静默容错 vs 显式报错:Flask 的 type=int 回落默认值会吞错,FastAPI 的 422 显式拒绝,API 对外服务时后者更安全。
  • 性能归因:快在 ASGI 异步架构与薄抽象,不在魔法;用错(async 里阻塞调用)反而更慢。
  • 决策路径:内容管理选 Django、高并发 IO API 选 FastAPI、小工具 Flask、JS 全栈 Express,按项目形态而非流行度定。
  • 反面清单:重后台、CPU 密集、无类型纪律团队,三种情况下 FastAPI 不是好答案。
  • 迁移账:Flask 迁移改的是边界层不是业务层;隐性宽容消失会让脏数据当天暴露;同步依赖可先跑同步路由再分批异步化。
  • 生态实况:存量资料 Flask 与 Django 更厚;新场景(异步客户端、模型服务、Serverless)FastAPI 是默认前提;集成靠依赖注入而非插件体系。

选型定了,下一节动手:把虚拟环境、依赖与开发服务器搭起来,顺便对比一下 venv、virtualenv、conda 三套隔离工具该怎么选。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U