请不要把 Flask 和 FastAPI 放到一起比较

The war has begun. Let’s end the war between Flask and FastAPI.

去年在知乎上看到一篇文章,后来想找却找不到了,最后通过关键词「5 分钟」和「四年」在掘金上找到了原文——《用它5分钟以后,我放弃用了四年的 Flask》。作者先是给出了一个手动用 if 验证请求数据的 Flask 视图函数:

@app.route('/insert', methods=['POST'])
def insert():
    info = request.json
    name = info.get('name', '')
    if not name:
        return {'success': False, 'msg': 'name 参数不可省略,不可为空!'}
    age = info.get('age', 0)
    if not isinstance(age, int):
        return {'success': False, 'msg': 'age参数不是数字!'}
    age_after_10_years = age + 10
    msg = f'此人名叫:{name},10年后,此人年龄:{age_after_10_years}'
    return {'success': True, 'msg': msg}

然后给出了一个使用 Pydantic 编写数据模型类 + FastAPI 路径操作函数的代码:

class People(BaseModel):
    name: str
    age: int
    address: str
    salary: float
    
@app.post('/insert')
def insert(people: People):
    age_after_10_years = people.age + 10
    msg = f'此人名字叫做:{people.name},十年后此人年龄:{age_after_10_years}'
    return {'success': True, 'msg': msg}

通过这两段代码进行对比,文章作者得出结论「我用了 Flask 四年,但在使用了5分钟 FastApi 以后,我决定以后不再使用 Flask 了」。你确定你用了四年的是 Flask 而不是 Flash?在此后的很长一段时间里,经常看到类似的对比和结论,所以我打算用这篇文章来澄清这个非常普遍的误解。如果看到有人把 FlaskFastAPI 放到一起比较,请把这篇文章的链接丢过去。

利益相关:笔者系 Flask 维护者和 APIFlask 作者。本文观点仅代表个人看法,与 Pallets 团队无关。

为什么 Flask 和 FastAPI 不能放到一起比较?

我在此前介绍 APIFlask 的文章里一开始就提到过这件事情:

经常看到有人把 FastAPI 和 Flask 放到一起比较,但是却没有意识到这完全是两种东西——前者是基于 Web 框架 Starlette 添加了 Web API 功能支持的(框架之上的)框架,而后者是和 Starlette 同类的通用 Web 框架。你怎么能让小明和骑电动车的小军赛跑然后还夸小军好快好强?

前两天看了 Reddit 上 Flask 2.0rc 帖子下的很多回复,预感到在 Flask 2.0 发布后在中文网络上也许会出现许多类似的问题和讨论,所以想着还是值得单独写一篇文章来谈这件事。

受那个 Reddit 主题里某个评论启发,想到一个更好的比喻: Flask 和 FastAPI 就像是苹果和橙汁。你不能把苹果和添加了更多蔗糖和添加剂的橙汁放在一起比较哪一个更甜。苹果(Flask)应该和橙子(Starlette)比较,橙汁(FastAPI)当然也应该和苹果汁(基于 Flask 的 Web API 框架)进行比较。也就是说,Flask 是一个通用型框架,和 FastAPI 依赖的 Starlette 一样,而 FastAPI 是添加 Web API 支持的二次框架。因此,Flask 应该和 FastAPI 所依赖的 Starlette 进行比较,而 FastAPI 应该和基于 Flask 的 Web API 框架进行比较。

同理,FastAPI 也不能和 Django、Tornado、web2py、Bottle、CherryPy、Pyramid、Sanic 等通用型 Web 框架比较。

更合理的比较对象是什么?

既然「FastAPI 应该和基于 Flask 的 Web API 框架比较」,那么合适的比较对象有哪些?Flask-RESTXFlask-Rebarflask-apispecflask-smorestFlask-RESTfulAPIFairy 这些虽然试图做成框架,但在具体实现上仍然是 Flask 扩展,所以真正公平合理的比较对象是:

这三个框架都是基于 Flask 实现的 Web API 框架。不过 Eve 没有内置 OpenAPI 支持,而 Connexion 是一个 Spec-First 框架,也就是 OpenAPI spec 优先(先编写 spec,然后生成项目基础代码进行开发)的框架。因此,尽管 APIFlask 只是一个刚发布没多久的实验项目,但它是最适合和 FastAPI 进行比较的项目。

FastAPI 和 APIFlask 有着同样的组成结构,都是作为一层胶水粘起来一个 Web 框架和一个数据序列化/验证库,然后加一点 Web API 功能支持:

在主要功能上也有很多交集:

  • 自动反序列化和验证请求数据,自动生成错误响应
  • 自动格式化和序列化响应数据
  • 自动生成 OpenAPI specification 文件
  • 自动生成交互式 API 文档

当然,相对于发布已经三年的 FastAPI,APIFlask 还只是一个三个月大的新项目,还有很多地方需要改进和完善。如果你对 APIFlask 感兴趣的话,可以在这篇文章了解详细介绍或是在它的 GitHub 仓库查看源码。

为什么会有这样的误解?

为什么会有那么多人把 FastAPI 和 Flask 放到一起比较?在我看来有三个原因:

首先是 FastAPI 采用了和 Flask 类似的装饰器路由,很容易让人联想到 Flask。

二是 FastAPI 没有给它的项目构成做足够的说明。如果 FastAPI 在其介绍的第一句就加上「based on Starlette and Pydantic」而不是放到 Requirements 部分才提及,这样也许会让 StarlettePydantic 获得更多应有的关注,也就不会有这么多人拿 Flask 和它比较。

再就是 FastAPI 的推介者对 FastAPI 了解的不够多。不清楚它的项目结构,自然就不了解它和其他 Web 框架的区别。而介绍时为了带来足够的吸引力,推介者常常选择拉上和用户量足够大的「竞品」进行对比这种方式,所以才会有大量类似本文开头提到的那种文章。这些文章甚至完全没有提及 Starlette 和 Pydantic,只是一味强调「这是一个超高性能的、碾压 Flask 的框架」。

FastAPI 不是银弹

FastAPI 只是说它能把「功能开发速度提升约 200% 至 300%」,距离十倍还差了一些,自然不能算是银弹。但是这两年看到了很多对 FastAPI 的盲目吹捧,仿佛 FastAPI 就是完美的解决方案。这也许都要归功于 FastAPI 在其 README 和文档里「大胆」的措辞和承诺以及不厌其烦的特性介绍。举例来说,如果它只是说「Very high performance, it is comparable to some frameworks in Go and NodeJS」,那么用户也许就会认为「和 Go/NodeJS 有一拼」,但是说成「Very high performance, on par with NodeJS and Go」,那么用户就会想「和 Go/NodeJS 不相上下」(以至于中文翻译是「可与 NodeJS 和 Go 比肩的极高性能」),这当然会带来一些争议。再比如,如果只是说「double or triple the development speed」,那么用户大概会想「效率还不错」,但是它用了「Increase the speed to develop features by about 200% to 300%」,数学不好的用户就会惊呼「哇,提高 30 倍开发效率」。同时这种营销带来的狂热用户,也很容易被煽动去给 Python Steering Council 施加压力——在 Python 3.10 快要发布的时候突然跳出来要求撤销一个 PEP。

这种营销至上的项目运营方式带来的直接后果是糟糕的维护状态。FastAPI 似乎在慢慢变成一个翻译项目,在代码里的文档字符串都还没写的情况下,其用户文档就已经开始扩展到十几种语言。而这十几种语言的翻译和项目源码都放在同一个仓库里,这导致所有开启的近 300 个 PR 里有过半是翻译。issue tracker 和 discussion 不做区分的使用,这导致所有开启的近 600 个 issue 里有九成是提问)。我在此前创建过一个 issue 来反馈这个问题,但并没有得到回应。另外凭个人喜好来说,在每个 commit 信息里都加上 emoji 并不可爱。而每一个 commit 都要触发 bot 更新 changelog,带来的是一份丑陋的 commit 历史(所有 commit 里有三分之一是在更新 changelog)。这些也许是发展社区或是让项目看起来很活跃的有效方法,但很显然这带来的是建立在混乱之上的虚假繁荣。

另外,许多推介文章都会在最后贴出来一张 benchmark 截图来证明 FastAPI 有多么 fast,但是完全不会提及的是:贴出来的 benchmark 对于开发生产应用来说有多大的意义?这里的 benchmark 背后有没有任何的 hacky?异步是否等同于高性能还是要看情况?框架本身的性能在一个请求处理流程中占多大的影响?asyncio 的生态怎么样?

从长远看这些大都是一些临时问题,而且 FastAPI 作者已经开始全职开发开源项目,这些问题在未来应该都会慢慢得到改善。指出这些是希望更多的人可以客观看待 FastAPI,吹捧并不能让一个东西变得更好,参与开发、介绍用法和回答社区提问是比盲目吹捧更有意义的事情。我当然期待 FastAPI 能够越来越好,也期待看到有更多优秀的 Python 框架出现,但我不喜欢过度炒作、盲目的吹捧和错误的对比。

P.S. Flask 2.0 版本即将发布

顺便说一句,Flask、Werkzeug、Jinja 等 Pallets 项目都将在 5 月 11 日(PyCon 开始)之前发布下一个主版本,主要包含下面这些新特性(完整变动见 changelog):

  • Flask、Werkzeug 和 Click 添加了 type hints
  • Flask 实现了有限的 async/await 支持
  • Flask 支持嵌套蓝本
  • Flask 添加了快捷版本的 route 装饰器(即 @app.get@app.post 等)
  • Flask 支持在视图类上使用 route 装饰器(我前两天刚提交了一个 PR 来实现这个功能,暂时不确定是否会被合并,在这先提前说一下 :p)
  • Werkzeug 的 multipart 解析性能大幅提升

你可以使用下面的命令安装 Flask 2.0.0rc 版本进行测试:

 $ pip install --pre flask

欢迎反馈 bug 和提改进建议。关于 2.0 版本的详细介绍将会在发布后另外撰文说明。

延伸阅读

请不要把 Flask 和 FastAPI 放到一起比较》上有12条评论

  1. 头像Kar

    我想提个问题, 就是Flask更新到2之后, 原来Flask+geventwebsocket的代码好像出了点问题
    “`python
    from flask import Flask, request
    from geventwebsocket import websocket
    from geventwebsocket.handler import WebSocketHandler
    from gevent.pywsgi import WSGIServer
    from typing import Set

    app = Flask(“”)

    s: Set[websocket.WebSocket] = set()

    @app.route(‘/ws’)
    def ws_handler():
    ws = request.environ.get(‘wsgi.websocket’)
    s.add(ws)
    while 1:
    try:
    data = ws.receive()
    for _ws in s:
    _ws.send(data)
    except Exception as e:
    s.remove(ws)
    break
    if __name__ == ‘__main__’:
    srv = WSGIServer((‘0.0.0.0’, 5000), app, handler_class=WebSocketHandler)
    srv.serve_forever()
    “`
    具体的表现是 Chrome用Websocket连接服务器是连上马上断开的(状态1变3),并且ws_handler没有任何反应,Edge也是一样, 但是火狐可以正常工作

    回复
  2. 头像1

    看到 “你怎么能让小明和骑电动车的小军赛跑然后还夸小军好快好强?” 就没兴趣看下去了 感觉博主根本不是从使用者的角度在思考 而只是在维护自己开发者的身份 为了区分而区分

    第一性原理建议了解一下 对于现在的调包程序员 (他们才是正常的大多数) 有多少会去看源代码的 他们用着方便就会继续用 就这么简单的道理 他们不关心司机是开汽车还是开摩托 只要快速安全地到达目的地就够了

    别沉迷在自己的思维圈里了 想想 Python 为什么用的人越来越多吧

    回复
    1. 头像李辉 文章作者

      我不需要从使用者的角度思考,我写这篇文章是因为我认为应该区分,所以我写出来希望更多的人能够区分。这篇文章的目标受众是写那种错误对比文章的人,那种文章的读者,以及关心两者区别的用户。不关心两者区别的人,自然不会读这篇文章。

      回复
  3. 头像laggardkernel

    FastAPI作者全职开发开源项目?我信他个鬼!280个PR开着,大部分不是被拒,根本是没有任何回复。

    回复
  4. 头像Zhihao

    我也是这样认为的。Flask2.0中增加了对异步的支持,也有像APIflask这样基于flask的专门编写API的二次框架,无论是开发效率和性能并不比fastAPI差。

    回复
  5. 头像five

    本质是FastAPI有更好的开发体验,就跟Vue.js的流行一样,把人聚起来了,所有问题就不是问题了。
    《用它5分钟以后,我放弃用了四年的 Flask》是利用了网络传播里的规则和技巧写出来的文章。现在的网络环境,像作者这种能保持客观和理性的,算一股清流了。

    回复
  6. 头像青南

    我是《用它5分钟以后,我放弃用了四年的 Flask》的作者。感谢博主的澄清。其实我写这篇文章的用意,并不是对比两个框架谁好谁不好。我想表达的就是我用 FastAPI,用起来很爽很舒服。仅此而已。就像博主举的例子,苹果和橙汁。确实,苹果和橙汁都不是同一类东西,看起来是不应该直接拿来比较。但是,这不妨碍橙汁比苹果味道好,所以我喜欢喝橙汁,不喜欢吃苹果。我并不关心橙汁是基于什么做出来的,我只关心谁的味道好。以后如果有一个框架基于 Flask,并且有比 FastAPI更好的用户体验,那我也会毫不犹豫转过去的。

    回复
    1. 头像李辉 文章作者

      很抱歉把你的文章挑出来作为反面示例,希望没有冒犯到你。

      苹果和橙汁可以比较,但这种比较是不合理不公正的(这是我想来澄清这件事的主要原因)。成年人和小孩子当然可以比武,但是这种比武不是建立在对等实力上的。我个人认为推介 FastAPI 更合理的行文方向有两种:单独介绍 FastAPI 的特性(不用和谁比较);介绍 Flask 搭配某些 REST API 扩展相对于 FastAPI 的劣势(公平合理)。

      > 以后如果有一个框架基于 Flask,并且有比 FastAPI 更好的用户体验,那我也会毫不犹豫转过去的。

      欢迎试试 APIFlask,不过它还是个新项目,或许用户体验并不优于 FastAPI。

      回复
  7. 头像Aber

    fastapi 那个宣传语,如果是日本产品,能被法院直接强制下线,如果是中国产品,足以让法院勒令修改。

    另外,很期待更快的 flask。我一直没弄明白 flask 这么慢的理由,我看源码它有很多地方为了性能而写的很别扭。

    回复
    1. 头像李辉 文章作者

      第一次见到感觉最不靠谱的是「开发速度提高 200%~300%,减少 40% 人为错误」,然后备注数据出自内部开发团队测试。

      同样期待更快的 Flask。欢迎提 PR 改进它,或者去 Pallets Discord 的 Flask 频道聊一聊这些问题。

      回复
      1. 头像Aber

        刚又看了一眼一个榜单,flask 居然有 django 三倍速了,是我落伍了。待会研究研究 flask 最近做了什么更新。

        回复

李辉进行回复 取消回复

电子邮件地址不会被公开,必填项已用 * 标出。