Flask 与 FastAPI 的差异
Flask 与 FastAPI 的用法差异和选型。
1. 两个框架的定位
Flask 是轻量 Web 框架,自带路由、模板和 session。
模板:带变量的页面文件。Flask 用 Jinja 模板引擎把用户名等数据填入模板,生成 HTML 页面。
session(会话):在多次请求之间保留用户状态。例如登录后保存用户 ID,访问其他页面时仍能识别这个用户。
FastAPI 主要用于写 API,用 Starlette 处理 Web 请求,用 Pydantic 校验数据。
Starlette:一个支持异步的 Web 框架。FastAPI 使用它提供的路由、请求与响应处理、WebSocket 等功能。
Pydantic:Python 数据校验库,按声明检查字段类型和取值限制。例如要求
text是字符串,且不能为空。
FastAPI 把参数声明、校验和文档放在一套写法里;Flask 的请求体校验和接口文档则需要自己实现或接入扩展。
2. 参数、校验与响应
2.1 GET 与 POST 接口示例
GET /get?page=1&size=2:读取 URL 中的page和size,在响应的args字段中返回这两个参数。POST /post:读取 JSON 请求体,在响应的json字段中返回数据。
运行环境:Python 3.13.11、Flask 3.1.3、FastAPI 0.156.4、Pydantic 2.14.1、Uvicorn 0.54.0。
2.1.1 Flask
Flask 从 request 中读取数据。这里的 POST 接口只检查请求体是不是 JSON 对象,不检查其中的字段。
| |
启动:
| |
另开终端请求:
| |
| |
2.1.2 FastAPI
FastAPI 的 POST 接口按 TextBody 校验 text 字段,只把模型中的字段放进响应。模型未声明的额外字段默认会被忽略。
| |
启动:
| |
另开终端请求:
| |
| |
传入空字符串时,FastAPI 在进入 post_json 前返回 422:
| |
| |
loc 指出错误在请求体的 text 字段,input 是触发错误的值。
| 对比项 | Flask 示例 | FastAPI 示例 |
|---|---|---|
| 校验规则 | 只检查请求体是不是 JSON 对象 | 按 TextBody 检查 text 的类型和长度 |
{"text": ""} | 返回 200,json 中保留空字符串 | 返回 422,不执行路由函数 |
| 增加字段校验 | 在项目中编写检查逻辑,或接入 Pydantic 等工具 | 在模型中声明规则,由框架执行 |
| 接口文档 | 需要另接文档工具 | 根据模型自动生成 |
2.2 路径、查询和请求体
- 路径参数:
/records/1中的1,对应record_id。 - 查询参数:
?q=hello中的hello,对应q。 - 请求体:
{"text": "hello"},对应Record模型。
FastAPI 接口样例:
| |
Path、Query 和模型里的 Field 都在路由函数执行前校验,失败时返回 422。
Flask 对应的取值方式:
| 参数 | 写法 |
|---|---|
| 路径参数 | <int:record_id> |
| 查询参数 | request.args |
| JSON 请求体 | request.get_json() |
路径中的 {record_id}、<int:record_id> 都是参数占位符。 请求 /records/3 时,函数收到 record_id=3。
- Flask 的
<int:record_id>把路径内容转成整数。写成<int(min=1):record_id>后,0不匹配路由,返回 404。 - FastAPI 的
Path(ge=1)要求编号至少为 1。0能匹配路径,但参数校验不通过,返回 422。
2.3 响应模型与状态码
FastAPI 用 response_model 校验响应数据并筛选字段,用 status_code 设置成功响应的状态码。
| |
首次提交 {"text": "hello"},返回 201 和以下内容:
| |
Flask 则在视图中构造响应字段,并返回状态码。
下面样例用 Pydantic 校验同一份输入:
| |
同样首次提交 {"text": "hello"},返回 201 和以下内容:
| |
FastAPI 应用启动后的文档地址:
/docs、/redoc:可浏览的接口文档。/openapi.json:描述接口的 OpenAPI JSON。
2.4 错误处理
| 情况 | FastAPI 默认结果 | 由谁判断 |
|---|---|---|
/records/0,编号不满足 ge=1 | 422,detail 是校验错误列表 | 框架按参数声明校验 |
/records/99,内存中没有这条记录 | 404,{"detail": "Record not found"} | 视图抛出 HTTPException |
参数格式合法,不代表记录存在或用户有权限。 后两项需要业务代码判断。
注册 RequestValidationError 处理器,可以把参数校验失败的响应改成 400:
| |
接口收到 /records/0 时返回 400 和 {"error": "参数不合法"};收到 /records/99 时仍返回 404。
要改这类 404 的响应格式,还需单独处理 HTTPException。
Flask 的 abort(404) 默认返回 HTML。API 要返回 JSON,可以通过 @app.errorhandler 注册处理器。
3. WSGI、ASGI 与并发
3.1 两套调用约定
WSGI(Web Server Gateway Interface)约定服务器与同步应用的调用方式:服务器传入请求信息,应用返回响应体。
ASGI(Asynchronous Server Gateway Interface)使用异步调用,应用通过 await 接收请求消息、发送响应消息。
| 方面 | WSGI | ASGI |
|---|---|---|
| 调用方式 | app(environ, start_response) | await app(scope, receive, send) |
| 响应方式 | start_response 设置状态码和响应头,返回字节串可迭代对象作为响应体 | 通过 send 发送状态码、响应头和响应体消息 |
| 协议范围 | HTTP 请求与响应,不定义 WebSocket 交互 | HTTP/1.1、HTTP/2、HTTP/3 和 WebSocket;实际支持情况还要看服务器 |
| 常见服务器 | Gunicorn、uWSGI | Uvicorn、Hypercorn |
ASGI 应用在事件循环中执行。等待异步 I/O 时,它能交回控制权,让其他请求继续运行。直接调用阻塞库则会卡住事件循环,async def 本身不会消除阻塞。
单线程同步 worker 要等当前请求结束,才能处理下一个;异步 worker 能在等待 I/O 时推进其他请求。
WSGI 不限制一个进程只能处理一个请求,并发方式由服务器安排。 Flask 常见的部署方式有多进程、多线程;使用 gevent 时,则通过协程和与其配合的 I/O 调用处理多个连接。
3.2 Flask 的 async
Flask 2.0 起支持 async def 视图,需要安装 flask[async]。
普通 WSGI 部署下,Flask 在线程里启动事件循环,执行异步视图。负责这个请求的工作线程,要等视图完成才能返回响应。
这允许一个请求并发等待几个异步接口,但只把视图改成 async def,不会增加同时处理请求的数量。
3.3 同步函数和异步函数
Depends用来声明接口依赖的函数。FastAPI 调用这个函数,把结果传给接口参数,例如数据库对象或当前用户信息。
FastAPI 调用路由和依赖函数时,按声明选择执行位置:
| 函数声明 | 执行位置 | 常见用法 |
|---|---|---|
async def | 事件循环 | 调用支持 await 的数据库或 HTTP 库,或只处理少量内存数据 |
普通 def | Starlette 线程池 | 调用同步阻塞库,等待时占用工作线程 |
通过 Depends 声明的依赖函数遵循同一规则。同步路由在等待数据库时,事件循环能继续处理其他请求,但线程池满了以后,新的同步调用就要排队。
同步依赖与异步路由的组合:
| |
Depends(get_message)把函数交给 FastAPI 调用,返回的"hello"会传入message。 这里传函数本身,不写get_message()。
启动:
| |
另开终端请求:
| |
| |
自己直接调用的普通函数,不会被 FastAPI 自动移到线程池。 在 async def 路由里直接调用同步数据库函数,阻塞的仍是事件循环线程。
如果路由内部的同步函数只做少量内存操作,例如 get_message() 只返回一个字符串,路由仍适合写成 async def,直接调用这些函数无需线程池。
示例使用的是 Depends(get_message),同步依赖仍在线程池执行。 路由的 async def 不会改变依赖函数的执行位置。
3.4 单 worker 的时间线
下面两个接口都等待 1 秒。Flask 版本阻塞当前线程:
| |
FastAPI 版本在等待时让出事件循环:
| |
假设 Flask 使用一个单线程同步 worker,FastAPI 使用一个异步 worker,同时收到 4 个请求,且没有额外的业务计算:
| |
时间线只计算等待时间,没有计入网络和调度开销。Flask 使用多 worker 或多线程时,请求能同时等待。
函数声明和等待方式都会影响 FastAPI 的并发:
| 路由写法 | 执行方式 |
|---|---|
def + time.sleep(1) | 路由进入线程池。空闲线程足够时,4 个请求能同时等待,每个请求占一个线程 |
async def + time.sleep(1) | 阻塞事件循环线程,同一进程里的其他异步任务也无法继续 |
async def 不会让 CPU 计算变快。 大量 Python 计算可以交给多个进程或独立计算服务;任务队列负责安排任务何时、由谁执行,不能直接缩短计算本身的耗时。
4. 依赖注入与生命周期
4.1 Depends 与 yield
多个接口共用数据库对象时,FastAPI 用 Depends 声明对象由哪个函数提供。框架调用这个函数,把它提供的对象传入视图。
示例中的 DB 仅模拟数据库:
| |
yield之前创建对象,yield之后清理对象。这里采用默认的请求作用域,清理发生在响应发送完成后。
同一请求多次引用同一依赖时,默认复用它的结果。 依赖函数需要的其他依赖,同样写在参数声明中。
如果视图返回后就不再需要这个对象,Depends(get_db, scope="function") 会在视图结束后、发送响应前清理。流式响应若还需要访问对象,就应保留默认的请求作用域。
Flask 用 g 存放当前上下文里共用的对象,视图显式调用 get_db():
| |
g 属于当前应用上下文,Flask 处理请求时会自动建立这个上下文。第一次调用 get_db() 创建对象,后续调用取回同一个对象;上下文结束时,teardown_appcontext 执行清理。
| 对比项 | Flask 示例 | FastAPI 示例 |
|---|---|---|
| 获取对象 | 视图调用 get_db() | 参数中声明 Depends,框架传入对象 |
| 同一请求内复用 | 保存在 g 中 | 默认缓存依赖结果 |
| 清理对象 | teardown_appcontext | yield 后的清理代码 |
4.2 应用启动与关闭
数据库连接池、深度学习模型通常在应用启动时准备,关闭时释放。FastAPI 的 lifespan 参数用于注册资源管理函数:
| |
@asynccontextmanager 把带有 yield 的异步生成器函数包装成异步上下文管理器。manage_resources 是自定义函数名,lifespan 则是 FastAPI 的参数名。
yield之前:启动时准备资源。yield之后:正常关闭时释放资源。
每个 worker 进程都会运行自己的生命周期。开 4 个 worker,就可能创建 4 份连接池或加载 4 份模型,数据库连接数和内存占用也会增加。
Flask 的资源管理要区分三个时机:
- 创建应用:在 app factory(创建并配置应用的函数)或扩展初始化中准备资源。
- 应用上下文结束:
teardown_appcontext清理当前上下文的资源,不是进程退出回调。 - 进程退出:由部署服务器钩子等机制安排清理。
旧教程中的 before_first_request 已在 Flask 2.3 移除。
5. 路由组织
APIRouter 用于整理一组接口、共享配置。 不使用它,直接在 app 上注册路由,接口照样能处理请求:
| |
APIRouter 中的 /records 前缀和文档分组只写一次,include_router() 负责把整组路由注册到 app:
| |
两版的 URL 和返回内容相同。APIRouter 减少的是公共配置的重复,通常也便于把一组路由单独放进模块;拆文件本身不要求使用它。
Flask 的 Blueprint 采用类似的分组和注册方式,下面仍提供 /records 和 /records/1 这样的路径:
| |
6. 后台任务
FastAPI 的 BackgroundTasks 在响应发完后调用任务函数,适合不必让客户端一直等待的短任务。 下面的 /notify 返回确认消息后,在服务端打印一条通知:
| |
| |
| |
| |
| |
任务仍在应用进程里执行;进程退出时尚未完成的任务,不会自动保存并在下次启动后续跑。
通知如果不能丢、失败后需要重试,就需要保存任务状态的队列和执行进程,例如 Celery 或 RQ。
Flask 没有对应的内置后台任务接口,通常接任务队列。若自行启动线程执行短任务,线程管理和应用关闭时的处理也要由应用安排。
7. 认证
认证用于确认这次请求代表哪个用户。 客户端登录成功时获得一段 token,后续请求带上它。服务器验证通过,请求才能进入需要登录的接口。
请求头中的 Bearer 凭据:
| |
Authorization是传递认证凭据的请求头;Bearer表示客户端凭持有的 token 发起请求。这里用固定的secret演示读取和验证,省略登录及签发过程。
FastAPI 用两层 Depends 连接这些步骤:
| |
客户端只发送 token;user 来自服务端的验证函数,并不是客户端直接提交的参数。
| |
| |
| |
| |
没有 Bearer 凭据时,HTTPBearer 返回 401;token 不是 secret 时,get_current_user 返回 401。这两种情况下,me 都不会执行。
取出 token 不等于验证通过,HTTPBearer 不知道它属于哪个用户,也不会替应用检查它是否有效。
在 /docs 点击 Authorize 并填入 secret 后,文档页面会为请求加上同样的认证头。
Flask 的 request.authorization 提供解析后的认证信息,以下示例在视图里直接验证:
| |
| |
| |
| |
- Flask:在视图里读取和检查凭据。多个接口共用验证逻辑时,通常提取到装饰器或请求钩子。
- FastAPI:用
Depends声明验证依赖,验证通过后,框架把用户信息传入视图。
实际应用需要在登录时签发凭据,并在请求中验证凭据,例如检查 token 是否过期。固定字符串比较只用于演示,不能代替这些验证。
8. 长连接
8.1 WebSocket
WebSocket 让客户端和服务器在同一条连接上持续收发消息。
FastAPI 的回显接口如下:
| |
客户端每发一条文本,服务器就回复一条带 echo: 前缀的文本,然后继续等待。Uvicorn 需要 WebSocket 协议实现,安装 uvicorn[standard] 会带上相关依赖。
Flask 通常用 Flask-Sock 等扩展,配合支持 WebSocket 的服务器。Flask-SocketIO 使用 Socket.IO 协议,需要匹配的客户端,不能直接当作普通 WebSocket 接口连接。
8.2 SSE
SSE(Server-Sent Events)用于服务器单向推送进度、日志或通知。 它保持一个 HTTP 响应,以 text/event-stream 格式发送事件。
下面的 Flask 和 FastAPI 接口都每隔约一秒发送一条消息,发完三条就结束响应。
Flask 通过生成器逐次返回消息:
| |
| |
| |
| |
FastAPI 的异步生成器在等待时让出事件循环:
| |
| |
| |
| |
data:后面是消息内容,空行分隔事件。输出块里的#仅用于标注运行结果,不属于 SSE 消息。
两版返回相同内容,等待时占用的资源不同:
| 运行方式 | 等待消息期间 |
|---|---|
| Flask,单线程同步 worker | 整条响应占住一个 worker |
| Flask,多线程 | 占住一个工作线程,其他线程继续处理请求 |
| Flask,gevent | 配合兼容的 I/O 调用,通过协程处理多个长连接 |
| FastAPI 异步生成器 | await 时让出事件循环,其他请求继续执行 |
X-Accel-Buffering: no 是应用返回给 nginx 的响应头,表示这次响应不要缓冲,收到数据就转发。
nginx 位于应用和客户端之间;开启响应缓冲时,SSE 消息可能在 nginx 暂存,客户端就看不到逐条到达的效果。
这个响应头由 nginx 识别,浏览器不用处理。若 nginx 配置为忽略它,就在该接口对应的 location 中设置 proxy_buffering off;。
- 长时间没有消息时,定期发送
: ping\n\n,让连接保持活跃。 - 生成器持有的订阅或连接在
finally中释放,并检查客户端断开连接时能否完成清理。
客户端和服务器都需要持续主动发消息时,例如协作编辑,用 WebSocket 更直接。
9. 部署
Flask 使用 Gunicorn:
| |
FastAPI 使用 Uvicorn:
| |
两条命令都占用 8000 端口,试跑时分别启动。
| 对比项 | Gunicorn 命令 | Uvicorn 命令 |
|---|---|---|
| 工作进程数 | -w 4,四个进程 | --workers 4,四个进程 |
| 每个进程的执行方式 | 默认同步 worker,同一时间处理一个请求 | 各自运行事件循环;同步路由交给线程池 |
进程数增加,内存和数据库连接的占用也会增加。 接口慢在数据库查询时,应检查慢查询和连接池,不能只靠增加 worker。
反向代理需要配置转发、健康检查和日志;WebSocket、SSE 还要检查超时和响应缓冲。
10. 框架选型
项目大小不能单独决定选型。Flask 能用于大型服务,FastAPI 也适合小型 API。
简单接口即使主要调用同步库,两者也都能胜任。
| 主要需求 | 优先考虑 |
|---|---|
| 已有 Flask 项目、依赖其扩展,或团队更熟悉 Flask | Flask |
| 需要自动参数校验和 API 文档,或有大量并发 I/O 请求且使用异步库 | FastAPI |
高并发场景下,要区分 I/O 等待和 CPU 计算。 请求主要在等数据库或外部接口时,异步 I/O 能利用这段等待处理其他请求;如果主要耗在 CPU 计算上,换框架不会自动提速。
评论
使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。
站长管理删除会同步到 GitHub,无法撤销。
阅读到这里时加载评论。