Skip to content

FastAPI 与 Flask 深度对比:从 WSGI/ASGI 底层到选型决策 ​

一句话结论:Flask 是「同步微框架 + 自己拼装」,FastAPI 是「异步框架 + 类型驱动 + 自带文档」。 差别不在功能表上谁多谁少,而在底层接口——Flask 建在 WSGI 上,FastAPI 建在 ASGI 上。

这篇文章不复述官方文档的功能清单,而是回答三个实际问题:

  1. 两者到底本质差在哪?
  2. FastAPI 是不是一定更快?
  3. 我手上这个项目该选哪个?

1. 本质分水岭:WSGI 与 ASGI ​

绝大多数「FastAPI vs Flask」的文章会从功能表讲起,但真正的分水岭是应用与服务器之间的接口协议。

1.1 Flask 与 WSGI:一次调用算完一个响应 ​

WSGI(Web Server Gateway Interface,PEP 3333)规定:服务器把请求打包成 environ 字典交给应用,应用在一次调用里返回一个可迭代的字节序列。

注意 C → D → E → F 这一段:它是同步阻塞的。数据库要 20ms、外部 API 要 200ms,这段时间 CPU 什么都没干,但处理这个请求的线程必须原地等着。

想提高并发怎么办?只能加线程或加进程:

  • 线程受 GIL 限制,且上下文切换有开销
  • 进程吃内存,一个进程几百 MB

结论:I/O 越重的服务,WSGI 的性价比越低。

1.2 FastAPI 与 ASGI:等待时可以切走 ​

ASGI(Asynchronous Server Gateway Interface)把接口改成基于协程的:应用是一个 async 可调用对象,接收 scope、receive、send 三个参数,可以反复 await。

关键在于:遇到 await 时,事件循环不会原地等,而是挂起当前请求、转去处理别的请求。等 I/O 完成后再回来继续。

这带来两个能力:

能力说明
高并发 I/O单个 worker 就能承载成百上千个并发连接,不必一个请求一个线程
协议可扩展scope["type"] 可以是 http、websocket、lifespan,所以 WebSocket、长连接是天然支持的

1.3 等待的时间去哪了 ​

判断该用哪种模型,关键不是「谁算得快」,而是等待期间谁被占着。下面这张图对比了同样 4 个并发请求、每个都要等 200ms I/O 的两种情况:

WSGI 同步阻塞模型与 ASGI 事件循环模型处理并发请求的对比架构图

左边每等一次 I/O,就有一个线程被白白占住;右边只有一个事件循环,等待期间它转去处理别的请求。

把镜头拉近,看单个请求的切换时机:

同步模型下,A 和 B 只能一个等完再轮到另一个;异步模型下,等待时间被重叠。这不是「算得更快」,而是「等得不浪费」。

⚠️ 核心认知:async 不是加速器。它只对 I/O 密集 场景有效;对 CPU 密集计算(图像处理、加解密、复杂算法)毫无帮助,反而会因为单线程事件循环被拖垮。


2. 同一个接口,两种写法 ​

抽象讲完,看具体代码。需求:POST /items 接收 {"name": "xxx", "price": 9.9},要求 name 非空、price 大于 0,成功返回 201。

2.1 Flask:校验、错误、状态码全手写 ​

python
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.post("/items")
def create_item():
    data = request.get_json(silent=True) or {}

    # 字段存在性 + 类型 + 业务约束,一层层自己判断
    name = data.get("name")
    if not isinstance(name, str) or not name:
        return jsonify({"error": "name 必填且不能为空"}), 400

    price = data.get("price")
    if not isinstance(price, (int, float)) or isinstance(price, bool):
        return jsonify({"error": "price 必须是数字"}), 400
    if price <= 0:
        return jsonify({"error": "price 必须大于 0"}), 400

    # 真实项目里这里还要写:错误码规范、日志、文档……
    return jsonify({"name": name, "price": price}), 201

这段代码能跑,但它暴露了几个问题:

  • 校验逻辑和业务逻辑混在一起,同一个模型在 10 个接口里要重复写 10 遍
  • 错误响应格式随人而异,前端没法统一处理
  • 接口文档是另一份文件,改代码忘了改文档是常态

2.2 FastAPI:把「长什么样」写成类型 ​

python
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class Item(BaseModel):
    name: str = Field(min_length=1)
    price: float = Field(gt=0)


@app.post("/items", status_code=201)
async def create_item(item: Item) -> Item:
    return item

对比一下,FastAPI 这段代码额外白送了:

能力Flask 版本FastAPI 版本
请求体解析手写 request.get_json()自动
类型转换手写 isinstance 判断自动("9.9" → 9.9)
字段约束手写 ifField(min_length=1, gt=0)
错误响应手写,格式随意自动 422,带字段级 loc / msg / type
响应过滤手写字典response_model 自动裁字段
接口文档需装扩展单独维护自动生成 /docs、/redoc、/openapi.json
编辑器补全无item. 之后自动补全 name / price

2.3 一个容易忽略的差别:函数签名即文档 ​

Flask 的参数来自 request 对象,是运行时才能知道的事情;FastAPI 的参数来自函数签名,是静态可读的。

这意味着 FastAPI 的参数来源可以被框架推导出来:

Flask 要达到同样效果,得引入 flask-smorest、webargs 之类的库,而且依然绕不开手写解析。


3. 能力来源不同:内置 vs 拼装 ​

这是我认为两者最本质的工程体验差异。Flask 是微框架,哲学是「给你最小内核,其余自己选」;FastAPI 是「内核该有的都给你,但 ORM 这类你仍然自己选」。

注意:FastAPI 不自带 ORM 和迁移,这一点常被误解。它和 Flask 一样要自己搭配 SQLAlchemy + Alembic,只是数据校验、文档、依赖注入、WebSocket 这几块省掉了。

3.1 依赖注入:FastAPI 最被低估的能力 ​

数据库会话、当前登录用户、分页参数、权限校验——这些横切关注点在 Flask 里通常靠装饰器、g 对象或蓝图钩子来凑。FastAPI 有一套声明式的依赖注入:

python
from typing import Annotated
from fastapi import Depends, HTTPException

CurrentUser = Annotated[User, Depends(get_current_user)]


def require_role(role: str):
    def checker(user: CurrentUser) -> User:
        if user.role != role:
            raise HTTPException(status_code=403, detail="权限不足")
        return user
    return checker


@app.delete("/posts/{post_id}")
async def delete_post(post_id: int, user: Annotated[User, Depends(require_role("admin"))]):
    ...

Depends 还天然支持嵌套、请求内缓存和测试覆盖(app.dependency_overrides),这是 Flask 生态里要额外找库才能达到的效果。


4. 逐项对比总表 ​

维度FlaskFastAPI
底层接口WSGI(同步)ASGI(异步,协程)
首个版本2010 年2018 年
定位微框架,内核极小现代 API 框架
异步视图2.x 起支持 async def,但底层仍是 WSGI,收益有限一等公民,async def / def 都可,语义明确
数据校验手工解析,或另配 Marshmallow / pydantic类型注解 + Pydantic 内置,自动 422
接口文档需扩展(APIFlask、flask-smorest)内置 OpenAPI + Swagger UI / ReDoc
依赖注入无(靠 g、装饰器、扩展)内置 Depends,含子依赖与缓存
WebSocket需 flask-sock 等扩展原生支持
ORM不自带不自带(同样选 SQLAlchemy / SQLModel)
模板渲染Jinja2 内置,成熟支持 Jinja2,但非主打
请求校验失败响应自己定标准 422 + 字段级错误定位
生态成熟度十几年积累,轮子多、Stack Overflow 答案多生态快速补齐,但选型需自己拍板
学习曲线极平缓,会 Python 就能写需理解类型注解、Pydantic、依赖注入、async
典型用户小工具、内部系统、传统 Web、教学API 服务、AI/LLM 后端、微服务、数据服务

5. 性能真相:FastAPI 一定更快吗? ​

不一定。 这是最容易被营销话术误导的地方。分清三种情况:

5.1 情况一:I/O 密集型并发 → FastAPI 明显更好 ​

大量请求在等数据库、等外部 API、等模型推理时,FastAPI 的事件循环能把等待时间重叠起来,同样的硬件能扛住高得多的并发。这是 FastAPI 的主场。

5.2 情况二:CPU 密集型 → 两者差不多 ​

如果你的接口主要在算(图片处理、加解密、复杂算法),异步帮不上忙。而且在 FastAPI 里把 CPU 密集任务写成 async def 会更糟——它会占住唯一的事件循环,把所有其他请求一起卡死。

python
# ❌ 在 async 函数里做阻塞操作:整个事件循环被卡住
@app.get("/bad")
async def bad():
    time.sleep(3)                 # 同步 sleep
    requests.get("https://...")   # 同步 HTTP 客户端
    return {"ok": True}
python
# ✅ 用异步库,或者干脆写成 def 交给线程池
@app.get("/good")
async def good():
    await asyncio.sleep(3)
    async with httpx.AsyncClient() as client:
        await client.get("https://...")
    return {"ok": True}

关键规则:用异步库就写 async def,用同步库就写 def。FastAPI 会自动把 def 函数丢进线程池执行,不会阻塞事件循环——这一点 Flask 用户很容易忽略。

5.3 情况三:单个简单请求的延迟 → 差距很小 ​

一个只返回 {"hello": "world"} 的接口,两者的延迟差别在真实业务里基本可以忽略。框架不是瓶颈,数据库和外部调用才是。

💡 一句话:FastAPI 的优势在并发吞吐,不在单请求速度。如果你的服务 QPS 只有个位数,性能不构成选型理由。


6. 选型决策 ​

把前面的分析收敛成一张决策树:

6.1 明确选 Flask 的场景 ​

  • 几十行的小工具、内部脚本、Webhook 接收端——不必为类型注解和异步付出认知成本
  • 团队已重度投入 Flask 专属扩展(Flask-Admin、Flask-Login、Flask-Security 那一套),迁移成本大于收益
  • 以服务端模板渲染为主的传统内容站
  • 教学场景:Flask 的心智负担确实更低

6.2 明确选 FastAPI 的场景 ​

  • 新写的 API 服务,需要给前端、移动端或第三方提供接口(OpenAPI 能直接生成客户端代码)
  • AI / LLM 后端:大量调模型、调外部 API、查向量库,等待时间远大于计算时间
  • 需要 WebSocket 或 SSE 流式输出(大模型流式响应几乎必然用到)
  • 微服务 / 数据服务:依赖注入 + Pydantic 让分层与测试都更清爽

6.3 两个都别选,去选 Django ​

需要开箱即用的后台管理、ORM、迁移、认证、权限、审计一体化的内容型项目,Django admin 能省掉几周,FastAPI 得从零搭。别用「FastAPI 更现代」说服自己重造后台。


7. 常见误区澄清 ​

误区事实
「FastAPI 自带 ORM」不自带。仍然要自己选 SQLAlchemy / SQLModel,迁移还是要 Alembic
「用了 async 就自动变快」只对 I/O 密集有效;CPU 密集无收益,写错反而更慢
「FastAPI 能直接替代 Django」缺 admin、ORM、模板优先等一体化能力,重后台项目不合适
「Flask 支持 async 就和 FastAPI 一样了」Flask 的 async 视图底层仍是 WSGI,事件循环模型受限,收益有限
「FastAPI 性能一定碾压 Flask」单请求延迟差距很小;优势体现在高并发 I/O 场景的吞吐
「Flask 过时了」没有。它仍在积极维护,且是大量存量系统的合理选择

8. 从 Flask 迁移到 FastAPI 的注意点 ​

如果你决定迁移,装饰器路由的写法相似,但下面几处需要重写思路:

Flask 写法FastAPI 对应写法
@app.route("/x", methods=["POST"])@app.post("/x")
request.args.get("q")函数参数 q: str | None = None
request.get_json()Pydantic 模型参数
jsonify(...)直接 return dict 或模型实例
abort(404)raise HTTPException(404, ...)
@login_requiredDepends(get_current_user)
app.before_request中间件或全局依赖

⚠️ 最容易翻车的一点:把同步数据库驱动(如 pymysql、psycopg2)直接搬进 async def 里。这会让整个事件循环阻塞,性能比 Flask 还差。要么换异步驱动(asyncpg、aiomysql),要么把这些函数写成 def。


9. 小结 ​

结论说明
分水岭是接口协议WSGI 同步阻塞 vs ASGI 协程并发,这决定了后面的全部差异
差别在工程体验FastAPI 把校验、文档、依赖注入、WebSocket 做成了内置能力
性能要看场景I/O 密集高并发 FastAPI 优势明显;CPU 密集和低 QPS 场景差距不大
选型看项目性质API 优先、I/O 密集、需要实时通信 → FastAPI;极简小工具、存量生态 → Flask;重后台一体化 → Django
没有银弹FastAPI 写得不对(async 里塞阻塞调用)会比 Flask 更慢

如果只记一句话:选 Flask 是因为「够用且简单」,选 FastAPI 是因为「接口优先且 I/O 密集」,选 Django 是因为「要的是完整的后台系统」。三者不是新旧替代关系,而是面向不同问题的工具。


📚 延伸阅读:FastAPI 的完整实战教程(19 章,从路由与参数到 SQLAlchemy 2.0、JWT 认证、测试与 Docker 部署)见 FastAPI 学习笔记。

📖本文阅读--次|📊全站访问--次|👥访客--人