标签归档:Flask

和捕蛇者说聊聊 Flask 2.0

六月初,应 laike9m 邀请做客捕蛇者说录了一期播客。今天中午发布了上半部分:Ep 30. 和李辉聊聊 Flask 2.0,关于自由职业的下半部分正在剪辑中,预计八月初发布。

这是我第一次录播客。录制那一天,房间里还没有装空调,而开风扇会导致风声录进去,我只能用静静降温法;为了防止录进邻居打骂孩子的声音,我又把门窗闭紧,所以录到临近中午,感觉越来越热。一开始还有点紧张和兴奋,但是我从来没有和别人聊过两小时那么久,录到后面,我就像漏气的气球一样,慢慢瘪了下去。

七月初拿到剪好的第一期,觉得可以再优化一下,所以就自告奋勇再剪一遍。花了接近十个小时,剪掉了很多「呃」、「嗯」、「对」、「然后」、「所以」……在这个过程里也发现不少自己说话上的毛病。

这一期上半部分聊了我从学习 Flask 到成为维护者的经历,这些在 19 年的 COSCUP 演讲简单介绍过。下半部分关于自由职业本来有不少内容想聊,但是因为说太多话有点累,所以有一些漏掉的话题就没有再提出来。录完的这些天有时会突然想到一些很有趣但忘记说的事情,偶尔还会想要不要再约着录一个补丁,也计划着写文章作补充。另外这一期虽然聊到了 FastAPI,但我对它了解并不多,如果有机会的话,以后还想再录一期详细聊聊 FastAPI 和 APIFlask,或是再聊一聊技术写作、翻译和演讲。

最后,如果你用过 Flask,欢迎来填写这个 Flask 用户调查问卷。关于捕蛇者说播客的更多信息,可以访问它的主页Twitter 了解。

下期:和捕蛇者说聊聊自由职业

开始翻译 Flask 文档

我在上周开始策划 Flask 文档翻译的事,制定了大致的翻译流程,创建了翻译协调仓库翻译模板仓库。中文翻译作为这个流程的试水项目,在上周末正式(静悄悄)启动。

blank

启动中文翻译项目的第一步是把 logo 里的标语「web development, one drop at a time」翻译成中文,这让我想起来一个也许冷也许不冷的冷知识。很多人知道 Flask 的名字是对 Bottle 的双关语——另一个容器/另一个 Python Web 框架。那为什么 Python Web 框架爱用容器做名字?其实是因为这些 Python Web 框架都是基于 WSGI 规范实现的,而 WSGI 不知从哪天起流行被读作 Whiskey(威士忌),所以这两个 Python Web 框架才叫做 Bottle 和 Flask,所以才有「Web 开发,一次一滴」。

欢迎在这些 discussions 中分别投票选出最佳翻译词汇、中文 logo 字体和中文标语。

回到正题。如果你对 Flask 足够熟悉,并且擅长翻译,欢迎参与进来!翻译通过 GitHub 进行协作(greyli/flask-docs-zh)。除了翻译文档,我们也需要有丰富翻译经验的朋友来审阅提交的翻译,欢迎在 GitHub 上关注(点 Watch 按钮)这个项目以接收新增翻译提醒。

下面是参与翻译之前要了解的三件事:

  1. 请务必完整阅读 Contributing Guide 了解翻译流程,然后阅读这个 issue 了解翻译要求。
  2. 需要强调的是,完成一章的翻译要提交两个 PR:第一个 PR 在 README 中对应的章节条目后添加自己的用户名以认领章节;第二个 PR 翻译对应的 .po 文件并勾选完成的章节条目。先认领,再翻译,一次只认领一章。
  3. 不要改动任何 ReStructuredText 标记、变量/类/函数/方法名称、URL 等。

如果有其他问题和相关想法,请创建 discussion 发起讨论;如果对翻译流程和项目设置有改进建议,或是发现了翻译错误和笔误,请创建 issue 反馈。

这个项目在后期会转移到 FlaskCWG 组织,并在翻译完成后链接到 Flask 官方文档。

Flask 2.0 版本发布

Flask 以及 Flask 依赖的 5 个 Pallets 项目都在今天发布了新的主版本(下面的链接指向各个项目的主版本变动日志):

​你可以使用下面的命令更新 Flask:

 pip install -U flask

如果你使用的国内 PyPI 镜像还没有同步最新版本,可以通过下面的命令临时切换到官方 PyPI 源:

 pip install -U flask -i https://pypi.org/simple/

这篇文章会介绍一些 Flask 新增的特性,完整的变动可以参考上面各个项目的变动日志。

三个核心特性

嵌套蓝本(#3923

对于一个比较大的项目,一般会使用蓝本来组织不同的模块。而如果你的项目非常大,那么嵌套蓝本就可以派上用场了。借助嵌套蓝本支持,你可以在某个蓝本之上再创建多个子蓝本,对项目进行多层模块化组织(而且支持无限嵌套,你可以嵌套很多层):

 parent = Blueprint("parent", __name__)  # 创建父蓝本
 child = Blueprint("child", __name__)  # 创建子蓝本
 parent.register_blueprint(child, url_prefix="/child")  # 把子蓝本注册到父蓝本上
 app.register_blueprint(parent, url_prefix="/parent")  # 把父蓝本注册到程序实例上

这样在生成子蓝本的 URL 时需要传入完整的端点链:

 url_for('parent.child.create')
 /parent/child/create

这个特性来源于一个 2012 年创建的 feature request issue

基本的 async/await 支持(#3412

Flask 2.0 带来了基本的异步支持,现在你可以定义异步视图(以及异步错误处理函数、异步请求钩子函数):

 import asyncio
 from flask import Flask
 ​
 app = Flask(__name__)
 ​
 @app.route('/')
 async def say_hello():
     await asyncio.sleep(1)
     return {'message': 'Hello!'}

注意要先安装额外依赖:

 pip install -U flask[async]

顺便说一句,如果你在 Windows 上使用 Python 3.8,那么会有一个来自 Python 或 asgiref 的 bug 导致出错:ValueError: set_wakeup_fd only works in main thread。可以通过下面两种方式(任选一种)处理(具体参考这个 SO 回答):

  • 升级到 Python 3.9
  • 在你的入口脚本顶部添加临时修复代码:
 # top of the file
 import sys, asyncio
 ​
 if sys.platform == "win32" and (3, 8, 0) <= sys.version_info < (3, 9, 0)::
     asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

不过目前只是一个基于 asgiref 的异步实现,作为异步支持的第一步,后续还会进行更多的优化和改进,更多相关信息可以参考文档

快捷路由装饰器(#3907

新增了下面的快捷路由装饰器:

  • app.get()
  • app.post()
  • app.delete()
  • app.put()
  • app.patch()

举例来说,使用 app.post() 等同于 app.route(methods=['POST'])

 from flask import Flask
 ​
 app = Flask(__name__)
 ​
 @app.post('/')
 def index():
     return {'message': 'Hello!'}

注意不要在这些快捷装饰器里传入 methods 参数。如果需要在单个视图处理多个方法的请求,使用 app.route()

我在某次 pallets 会议上提议添加这些装饰器时一开始是被拒绝的,后来 Phil Jones 创建了 #3907 经过二次讨论后才最终合并(被拒绝后我就把当时正在开发的 APIFlask 从扩展改成了继承 Flask 基类的框架,然后加了这些装饰器)。

我添加的三个特性

修复执行 flask run 找不到程序的奇怪设定(#3560

这个像 bug 又像是 feature 的设定我在《一个困扰我两年的 Flask「Bug」》里详细说过,最终终于在两年后修复了。

支持在 .flaskenv 和 .env 文件里写中文(#3932

我在《Flask Web 开发实战》第一章介绍 .flaskenv 文件时给了一个示例,演示如何添加注释:

SOME_VAR=1
# 这是注释

但这个示例没有实际测试……加了中文其实会报错,后来有两次收到读者反馈,最终终于在三年后修复了。

为文档添加命令切换面板(#3714

这个或许算不上特性,不过在我看来是对用户非常友好的变动。除了个别不需要区分操作系统和命令行程序的命令外,我给文档里所有的命令添加了支持切换 Bash/CMD/Powershell 以及 macOS/Linux/Windows 的切换面板(面板的样式后续会有一些优化):

blank

其他的有用特性

  • 优化了浏览器缓存控制,对 CSS、图片等静态文件做出的变动会在程序重载后立刻更新,不再需要手动清除页面缓存。
  • Werkzeug 的 multipart 解析(尤其是大文件上传处理)性能提高了 15 倍。
  • 配置对象增加 Config.from_file() 方法支持从任意文件加载器导入配置(比如 toml.loadjson.load),未来会取代 Config.from_json() 方法。
  • 在使用环境变量 FLASK_APP 指定工厂函数时支持传入关键字参数。
  • flask shell 支持 tab 和历史补全(需要安装 readline)。
  • CLI 系统优化了找不到程序时的错误处理和错误输出显示,同时修正了 Windows 上的命令行颜色输出。

破坏性变动

主要的破坏性变动(breaking change)是意外的把 send_from_directory() 函数的第二个参数名称直接由 filename 重命名为 path,将会在 2.0.1 加回来(#4019)。

另外 send_file() 函数的三个参数也进行了重命名(旧名称将在 2.1.0 移除):

  • attachment_filename -> download_name
  • cache_timeout -> max_age
  • add_etags -> etag

其他重要变化

  • 不再支持 Python 2 和 Python 3.5。
  • 所有 Pallets 项目都添加了 type hinting,这意味着更好的 IDE 自动补全体验。
  • 所有仓库的主分支由 master 改为 main。如果你在本地克隆了 Flask 等仓库,可以使用下面的命令来更新:
 git branch -m master main
 git fetch origin
 git branch -u origin/main main
 git remote set-head origin -a

感谢支持

这次更新对于整个 Pallets 项目来说是一个新的里程碑。接下来还有许多事情要做:FlaskCon 2021 正在准备中,新建立的 Flask 社区工作小组(Flask Community Work Group)正在进行 Flask 文档翻译(如果你对中文翻译感兴趣,可以订阅这个讨论)、被遗弃扩展收容计划(这是我一直想做的事情)等等。感谢支持,敬请期待!

欢迎通过下列途径关注 Pallets 项目:

相关文章:

请不要把 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 版本的详细介绍将会在发布后另外撰文说明。

延伸阅读

APIFlask:一个基于 Flask 的 Web API 框架

经常看到有人把 FastAPI 和 Flask 放到一起比较,但是却没有意识到这完全是两种东西——前者是基于 Web 框架 Starlette 添加了 Web API 功能支持的(框架之上的)框架,而后者是和 Starlette 同类的通用 Web 框架。你怎么能让小明和骑电动车的小军赛跑然后还夸小军好快好强?为了让框架 PK 爱好者们有一个更公平的比较对象,从一份 APIFairy 0.6.2 版本的 fork 开始,我实现了一个基于 Flask 的 Web API 框架——APIFlask

blank

APIFlask 在 Flask 的基础上添加了更多 Web API 相关的功能支持,核心特性包括:

  • 更多方便的装饰器,比如 @app.input()@app.output()@app.get()@app.post() 等等
  • 自动反序列化和验证请求格式,当请求数据不符合模式类要求时,会自动生成包含错误详细信息的错误响应(基于 Webargs
  • 自动格式化和序列化响应数据,在定义好响应模式后,你可以直接在视图函数返回一个模型类对象,或是返回字典(基于 Marshmallow
  • 自动生成 OpenAPI Specification 文件,你可以把这个文件导入到 API 调试工具或是用来生成客户端代码(基于 APISpec
  • 自动生成交互式 API 文档,并自动为蓝本和视图设置对应的标签分类(基于 Swagger UI and Redoc
  • 自动为 HTTP 错误生成 JSON 格式的错误响应

下面是一个最基础的示例程序:

 from apiflask import APIFlask, Schema, abort
 from apiflask.fields import Integer, String
 from apiflask.validators import Length, OneOf
 ​
 app = APIFlask(__name__)  # 可以使用 title 和 version 参数来自定义 API 的名称和版本
 ​
 pets = [
     {
         'id': 0,
         'name': 'Kitty',
         'category': 'cat'
     },
     {
         'id': 1,
         'name': 'Coco',
         'category': 'dog'
     }
 ]
 ​
 # 定义一个请求数据模式类
 class PetInSchema(Schema):
     name = String(required=True, validate=Length(0, 10))  # 可以使用 description 参数添加字段描述
     category = String(required=True, validate=OneOf(['dog', 'cat']))
 ​
 # 定义一个响应数据模式类
 class PetOutSchema(Schema):
     id = Integer()
     name = String()
     category = String()
 ​
 ​
 @app.get('/pets/<int:pet_id>')
 @app.output(PetOutSchema)  # 使用 @output 装饰器标记响应数据模式
 def get_pet(pet_id):
     if pet_id > len(pets) - 1:
         abort(404)
     # 在真实程序里,你可以直接返回 ORM 模型类的实例,比如
     # return Pet.query.get(1)
     return pets[pet_id]
 ​
 ​
@app.patch('/pets/<int:pet_id>')
@app.input(PetInSchema(partial=True))  # 使用 @input 装饰器标记请求数据模式
@app.output(PetOutSchema)
def update_pet(pet_id, json_data):  # 通过验证后的请求数据字典会注入到视图函数,默认参数名为 json_data
    if pet_id > len(pets) - 1:
        abort(404)
    for attr, value in json_data.items():
        pets[pet_id][attr] = value
    return pets[pet_id]

P.S. 你也可以使用类视图(class-based views),具体示例见这里

如果你想在你的电脑上运行这个示例,可以先用下面的命令安装 APIFlask(需要 Python 3.7 及以上版本,Flask 1.1 及以上版本):

Linux 和 macOS:

 $ pip3 install apiflask

Windows:

 > pip install apiflask

安装完成后把上面的代码保存到文件 app.py,然后执行下面的命令运行程序:

 $ flask run

现在你可以在浏览器访问 http://localhost:5000/docs 查看基于 Swagger UI 自动生成的交互式 API 文档:

Swagger UI

或者访问 http://localhost:5000/redoc 查看基于 Redoc 生成的 API 文档:

Redoc

访问 http://localhost:5000/openapi.json 可以获取自动生成的 OpenAPI spec 文件。

这个示例程序的完整版本可以在 https://github.com/greyli/apiflask/tree/master/examples 看到。如果你想了解更多用法,可以阅读文档中的基本用法一章(目前只有英文)。

APIFlask 只在 Flask 之上做了轻量级包装,所以你实际上仍然是在写一个 Flask 程序,所有 Flask 的特性都完全兼容。你只需要记住下面两处不同点:

  • 创建程序实例的时候使用 APIFlask 类(from apiflask import APIFlask)。
  • 创建蓝本实例的时候使用 APIBlueprint 类(from apiflask import APIBlueprint)。
  • 使用 apiflask.abort() 函数返回 JSON 格式的错误响应。

以下面的 Flask 程序为例:

 from flask import Flask, request, escape
 ​
 app = Flask(__name__)
 ​
 @app.route('/')
 def hello():
     name = request.args.get('name', 'Human')
     return f'Hello, {escape(name)}'

迁移到 APIFlask 只需要改动两行代码:

 from apiflask import APIFlask  # 第一行
 from flask import request, escape
 ​
 app = APIFlask(__name__)  # 第二行
 ​
 @app.route('/')
 def hello():
     name = request.args.get('name', 'Human')
     return f'Hello, {escape(name)}'

欢迎提出改进建议,报告 bug 或是分享其他任何相关的想法。你也可以在 GitHub 上创建 Issue,或是提交 PR 来改进它。谢谢!

FlaskCon 2020 参会指南

FlaskCon 是由社区举办的免费线上 Flask 会议,欢迎 Flask 的使用者和爱好者参加。这是一份非官方参会指南,希望可以帮到你。

会议时间

基于演讲者优先的考虑(因为听众可以看录播),为了让演讲者不用半夜爬起来演讲,所以有了现在的日程时间:

  • 7 月 4 号:晚上 10 点到第二天凌晨 2 点 20
  • 7 月 5 号:下午 5 点到晚上 9 点 20

议题

一共收到 44 个议题申请,按照两天的时间安排,最终只保留了 18 个。大致分为下面几类:

我们是怎么用 Flask 的?

这里的「我们」包括 Google Cloud、FEC(美国联邦选举委员会)、ScholarPack……

  • How Google Cloud uses Flask(Dustin Ingram)07/04 10:00 PM
  • How the FEC uses Flask to increase transparency in US elections(Laura Beaufort)07/04 11:00 PM
  • Lessons Learned Building Microservices with Flask(Joe Coburn)07/05 02:00 AM
  • OpenPatch an Example for Building Microservices with Flask(Mike Barkmin)07/05 06:00 PM

Flask 相关特性介绍

介绍 Flask 的灵活性、工厂模式、请求上下文、异步 Flask 实现——Quart……

  • The Application Factory Architecture(Bruno Rocha)07/04 10:30 PM
  • Demystifying Flask’s Application and Request Contexts with pytest(Patrick Kennedy)07/04 11:30 PM
  • Flask’s flexibility for the win(Brett Kromkamp)07/05 06:30 PM
  • Async Flask, an introduction to Quart(Philip Jones) 07/05 09:00 PM

用 Flask 开发 Web API

  • Flask-AppBuilder new REST API, and Superset MVC to SPA revamp(Daniel Gaspar)07/05 07:30 PM
  • Using Flask-RESTy to build flexible REST APIs, fast!(Jimmy Jia)07/05 01:30 AM
  • Securing Flask Web Applications with JWT and CSRF tokens(Solomon Esenyi)07/05 01:00 AM

先介绍这么多,完整的议题时间表各位可以在官方网站上的 Schedule 部分看到,议题信息里的时间即北京时间(GMT+8)。

前一天在晚上 11 点还会有两个小采访,不过看起来没什么意思。

直播和录播

会议会通过在 FlaskCon 的 YouTube 频道直播,录制的视频也会放到这里。如果你没法访问 YouTube,不用担心,墙外开花墙内也能闻得到,因为 PyCon China 的 bilibili 账号也会同时进行转播,后续视频也会放到这里。英语听不懂的话,YouTube 可以自动生成字幕,也可以实时翻译,如果你没法访问 YouTube,那你就要自己想办法了,试试重启路由器吧。

如何参加?

嗯……因为是免费活动,看直播就算是参加了。如果你想获得更多的临场感和参与感,可以加入 Pallets 的官方 Discord 服务器,在 #flaskcon 频道(会议期间开放)里参与讨论,向演讲者提问(每个演讲有 5 分钟的 QA 环节),记得用英语。另外也可以在这个非官方 Python Discord 服务器或是 Reddit 上的 Flask 节点参与讨论。如果你访问不了 Discord 和 Reddit,或者英语不好的话,天无绝人之路,你还可以在 PyCon China 的 bilibili 直播间刷礼物和发弹幕 :)

Update 2020/7/4 看起来 PyCon China 并没有转播 :/

Update 2020/7/7 PyCon China 没有转播是因为一个乌龙事件「They missed the date due to an error in our meeting notes」,最终改为 7/14 转播,并且(据说)会添加中文翻译字幕。

比修 Typo 还简单的开源贡献方式

最近给 WTForms 提交了一个 PR,这个 PR 向源码、测试和本地化文件里添加了 537 个句号。听起来似乎很奇怪,为什么 WTForms 会需要增加 537 个句号?别着急,下面会慢慢解释(事实上我只完成了一部分的工作,还有大概 500 个句号需要添加)。我发现我似乎很喜欢提交这种 PR,这类开源贡献没有太多技术含量(基本就是体力活),但是能有效提高项目的整体完美度,让用户获得更一致和舒服的体验。下面是一些可以归到这一类开源贡献的 PR。

1. 给 WTForms 添加 537 个句号

https://github.com/wtforms/wtforms/pull/620

WTForms 从添加 CSRF 保护功能作为分界,之前定义的验证错误消息都包含结尾的句号,之后的错误消息都漏掉了句号。这会导致错误消息不一致(想象同一个表单显示两个输入框的错误消息,一个有句号,一个没有句号)。这个 PR 补齐了源码、测试、本地化文本(POT 和部分 PO 文件)中错误消息的句号,包括 506 个英文句号和 31 个中文句号。

本地化文件在翻译时大都按照源文本决定是否添加句号,所以也存在错误消息不一致的问题。因为精力有限,在 32 个本地化文件里,我只更新了简体中文、繁体中文、日文、德文和俄文。大概还有二十几个本地化文件需要进行确认和更新,如果感兴趣的话,你可以考虑去做这件事。

2. 给 Flask 添加 96 个美元符号

https://github.com/pallets/flask/pull/2877

Flask 文档、和各类 README.md 文件里对于命令行命令的标识很混乱,有时没有命令提示符,有时用「>」,有时用「$」,有时用「#」。这个 PR 统一了所有命令行命令,统一添加美元符号作为提示符,仅在需要明确区分 Windows 命令的几处使用「>」。

3. 给 Flask 更换 13 个 URL

https://github.com/pallets/flask/pull/3427

Flask 去年陆续把文档迁移到了 palletsprojects.com 域名下, 访问旧的 pocoo.org 会进行跳转,这个 PR 更新了所有文档和源码里的旧 URL。

4. 给 PyCon ChinaFlaskCon 压缩 119 张图片

除此之外,勉强能沾上边的还有给 PyCon ChinaFlaskCon 的网站分别压缩了 105 和 14 张图片。因为缺乏规范,有些技术大会的网站图片在上传之前没有经过压缩和裁剪,这会让页面加载变得非常慢。这两个 PR(PyConChina #3FlaskCon #7)分别让两个网站的图片总大小从 25M 和 4M 降低到 5M 和 967KB。

给文档修 typo 是很常见的开源贡献类型,这也是很多人一开始参与开源的方式。有人甚至会走火入魔最后变成专业的「开源 typo 修复专家」,不停的用英文语法检查工具去检查每一个流行的开源项目文档……相比之下,上面这一类 PR 要比修 typo 更简单(在智力上),有时也更有价值。

我发现相对于技术实现,我其实更关注 API 设计和用户体验,总想要尽可能的追求设计的一致和美观。这也导致我会在写作和编程时花费大量时间在命名、文件组织、措辞、章节安排、排版、文案和彩蛋这些事情上。这大概就是我编程水平进步缓慢、写作速度缓慢的原因。

P.S. 文中的数字都是估算,大概会有 1~5 左右的偏差。精确的数字是为了让措辞看起来更一致和美观瞎编的。

欢迎参加 FlaskCon 2020!

FlaskCon 是一个社区举办的 Flask 大会,将会在 7 月 4 号举行(为期两天)。按照官方的介绍,它:

  • 100% Remote
  • 100% Free
  • 100% Community-driven

和 DjangoCon 类似,FlaskCon 会专注于 Flask 相关话题,包括 Flask 扩展介绍、开发经验、最佳实践、类似框架的对比等等。

如果你想分享 Flask 开发相关的经验,介绍你对 Flask 的有趣应用,或者是有任何和 Flask 相关而且你很想拿出来聊一聊的话题,欢迎报名演讲。演讲有 20 分钟和 40 分钟两种类型,需要使用英语。

你可能会觉得大会的 Logo 不是很好看……不要担心,这些设计还在慢慢改进中,如果你有任何和大会网站、议程设置、设计相关的建议,可以发邮件到 flaskcon@gmail.com,或是在 Discord 频道和 Reddit 主题帖里反馈。

P.S. 如果你想帮忙改进大会网站,最直接的方式是自己动手修改源码

相关链接:

一个困扰我两年的 Flask「Bug」

TL;DR 版本:

如果你的程序存储在单脚本里,比如 app.py,那么 .flaskenv 和 .env 应该放在程序脚本的同级目录:

myproject/
    - app.py
    - templates
    - static
    - .flaskenv
    - .env

如果你的程序存储在程序包里,那么 .flaskenv 和 .env 应该放在程序包的同级目录:

myproject/
    - app/
        - templates
        - static
        - __init__.py
        - views.py
    - .flaskenv
    - .env

如果你把 .flaskenv 和 .env 放到了再往上一层(myproject 再往上),或是把你的程序包或程序脚本放到了一个子目录(比如 /myproject/myapp),那么执行 flask run 就会出错。


严格来说算不上 bug,而是一个很容易导致出错的设计。具体行为是,如果你安装了 python-dotenv,同时在 Flask 程序的上层目录创建了 .env 或 .flaskenv 文件,那么你将没法成功执行 flask run 等命令,因为这会导致 Flask 没法正确找到对应的 Flask 程序实例。

这个问题从 Flask 开始引入 CLI 机制开始就存在了,困扰了我两年。18 年偶然在用户根目录创建了一个 .env 文件,发现 Flask 程序没法运行了,当时遇到的各种 bug 太多,没仔细考虑这两者之间的关联。后来经过几次测试,才确定下来是上层目录的 .env 和 .flaskenv 文件导致,但是一时找不到原因,就暂时放下了。直到 19 年 11 月,花了几个小时排查,还是没找到原因。

中间花了很长时间来追踪 Windows 特定的 Flask 程序无法启动的 bug(TypeError: environment can only contain strings),实在是怕了。因为 Flask 的 CLI 涉及太多东西,有时你要钻进 python-dotenv(#101) 和 Werkzeug(#1320) 才能找到问题的原因。

但是问题不解决的话,你永远睡不好觉。《Flask Web 开发实战》第一部分的示例程序都放在了一个程序仓库,而且都放在了子目录,这意味着如果读者错误的在仓库根目录创建 .env 和 .flaskenv 文件的话,就会导致子目录下的六个示例程序没法运行。前后大概收到 6 个相关的读者反馈,虽然后续在网站上添加了提醒,在重印的书里介绍创建 .env/.flaskenv 文件的地方追加提醒,但这终究没有真正解决问题,而且总会有人可以完美的错过所有提示。

如果每个人都可以在书上标记出错位置并共享,那么这一页应该会有很多红色小叉号(参考《超级马里奥制造》,也许未来某个电子书平台会做出来这个功能 :P)。

前几天在这个 Issue 的提醒下,又花了两个小时排查,这次终于找到原因。加上写 PR(#3560)和 Issue(#3561),前后两年一共花了 8 个小时,这个问题终于有了着落。没意外的话,预计会在下一个版本的 Flask 中更新。下面是具体原因。

本来以为这个问题和 python-dotenv 或 Werkzeug 相关,没想到只是 Flask 本身代码的问题,我太笨了,这个问题本可以早一点解决。

按照预定的行为,当安装了 python-dotenv,Flask 会自动加载 .env 和 .flaskenv 里的环境变量。python-dotenv 在搜索存储环境变量的文件时,会从当前目录开始向上搜索,如果找到就返回对应的文件路径。但是这时 Flask 如果发现 .env 或 .flaskenv 的所在目录不是当前目录,就会把当前工作目录切换到 .env 和 .flaskenv 所在的目录(相关源码)。而如果你的程序模块或程序包不是和 .env/.flaskenv 同级目录的话,就会导致找不到程序实例。

使用下面的步骤可以重现:

$ git clone https://github.com/greyli/flask-env-test
$ cd flask-env-test
$ pip install -r requirements.txt  # or just pip install flask[dotenv]
$ cd hello
$ flask run

示例项目的文件结构如下:

- flask-env-test
    - .env
    - hello
        - app.py

像示例程序这样把程序存储在 app.py 文件中时,运行 flask run 你会看到下面的报错:

$ flask run
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
Usage: flask run [OPTIONS]

Error: Could not locate a Flask application. You did not provide the "FLASK_APP" environment variable, and a "wsgi.py" or "app.py" module was not found in the current directory.

如果你使用 FLASK_APP 指定了程序的导入路径,那么错误大概会是这样:

$ flask run
 * Serving Flask app "myapp"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
Usage: flask run [OPTIONS]

Error: Could not import "myapp".

相关链接

安装 Python 依赖出现 MarkupSafe ImportError … Feature 报错的解决方法

这个报错在 3 月 8 号 setuptools 发布新版本之后出现,通常会在安装 Python 依赖时触发。

报错信息

使用 pip 安装依赖时的报错如下:

Collecting markupsafe==1.0
  Downloading https://.../MarkupSafe-1.0.tar.gz (14 kB)
    ERROR: Command errored out with exit status 1:
     command: '...\python.exe' -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'...\\pip-install-bsormril\\markupsafe\\setup.py'"'"'; __file__='"'"'...\\pip-install-bsormril\\markupsafe\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base '...\pip-install-bsormril\markupsafe\pip-egg-info'
         cwd: ...\pip-install-bsormril\markupsafe\
    Complete output (5 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "...\pip-install-bsormril\markupsafe\setup.py", line 6, in <module>
        from setuptools import setup, Extension, Feature
    ImportError: cannot import name 'Feature'
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

使用 Pipenv 安装依赖时的报错如下:

An error occurred while installing markupsafe==1.0 --hash=sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665! Will try again.
Installing initially failed dependencies…
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 1874, in do_install
[pipenv.exceptions.InstallError]:       keep_outdated=keep_outdated
[pipenv.exceptions.InstallError]:   File "...Python\Python36\site-packages\pipenv\core.py", line 1253, in do_init
[pipenv.exceptions.InstallError]:       pypi_mirror=pypi_mirror,
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 859, in do_install_dependencies
[pipenv.exceptions.InstallError]:       retry_list, procs, failed_deps_queue, requirements_dir, **install_kwargs
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 763, in batch_install
[pipenv.exceptions.InstallError]:       _cleanup_procs(procs, not blocking, failed_deps_queue, retry=retry)
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 681, in _cleanup_procs
[pipenv.exceptions.InstallError]:       raise exceptions.InstallError(c.dep.name, extra=err_lines)
[pipenv.exceptions.InstallError]: ['Looking in indexes: https://.../pypi/simple', 'Collecting markupsafe==1.0', '  Using cached https://.../MarkupSafe-1.0.tar.gz (14 kB)']
[pipenv.exceptions.InstallError]: ['ERROR: Command errored out with exit status 1:', '
command: \'...\\.virtualenvs\\helloflask-evdb6idn\\scripts\\python.exe\' -c \'import sys, setuptools, tokenize; sys.argv[0] = \'"\'"\'...\pip-install-pkgojp4t\\\\markupsafe\\\\setup.py\'"\'"\'; 
__file__=\'"\'"\'...\\\\pip-install-pkgojp4t\\\\markupsafe\\\\setup.py\'"\'"\';f=getattr(tokenize, \'"\'"\'open\'"\'"\', open)(__file__);code=f.read().replace(\'"\'"\'\\r\\n\'"\'"\', \'"\'"\'\\n\'"\'"\');f.close();exec(compile(code, __file__, \'"\'"\'exec\'"\'"\'))\' egg_info --egg-base \'...\\pip-install-pkgojp4t\\markupsafe\\pip-egg-info\'', '
cwd: ...\\pip-install-pkgojp4t\\markupsafe\\', '    Complete output (5 lines):', '    
Traceback (most recent call last):', '      
File "<string>", line 1, in <module>', ' 
File "...\\pip-install-pkgojp4t\\markupsafe\\setup.py", line 6, in <module>', '
from setuptools import setup, Extension, Feature', "
ImportError: cannot import name 'Feature'", '----------------------------------------',
'ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.']
ERROR: ERROR: Package installation failed...

其他工具输出类似,主要异常信息是 MarkupSafe setup.py: ImportError: cannot import name Feature。通常会在安装 Flask 项目的依赖时发生,因为 MarkupSafe 是 Flask 的依赖之一。

原因和解决方法

出现这个报错的原因是因为 Python 打包工具 setuptools 在 46.0.0 版本删掉了弃用的 Feature,而 MarkupSafe 刚好在 setup.py 文件里导入了这个类,所以会出现报错。

解决方法很多,最直接的是更新 MarkupSafe 到最新版本(1.1.1),新版本去掉了对 Feature 类的导入。如果使用 requirements.txt 存储依赖列表,那就把 MarkupSafe 的版本号改成 1.1.1(找到 MarkupSafe 开头那一行,替换版本号):

MarkupSafe==1.1.1

然后重新执行:

$ pip install -r requirements.txt

对于 Pipenv,可以直接执行:

$ pipenv install markupsafe==1.1.1

如果你是《Flask Web 开发实战》的读者,正在为第一部分的示例程序安装依赖,那你还需要执行下面的命令固定 sendgrid-python 的版本(它在新版本添加了不向后兼容的 API 变动):

$ pipenv install sendgrid==5.3.0

《Flask Web 开发实战》读者备注

如果你在 2020 年 3 月 8 号到 4 月 5 号之间买了《Flask Web 开发实战》,把示例程序克隆到了本地,然后尝试运行 pipenv install 或 pip install -r requirements.txt 命令来安装依赖,那很大概率你会遇到这个问题。

除了使用上面的方法之外,你还可以通过更新本地代码来解决。我最近给所有示例程序的依赖文件做了一次更新,除了书里涉及的 API 产生变动的依赖,其他依赖都已经更新到最新版本。

你可以使用下面的命令来更新你在本地的程序仓库(注意这会重置你对源码进行的修改):

$ git fetch --all
$ git fetch --tags
$ git reset --hard origin/master

然后重新执行一次 pipenv install 或 pip install -r requirements.txt 即可解决这个问题。如果遇到其他虚拟环境和依赖安装的问题可以参考这篇文章解决。

相关链接:

《Flask Web 开发实战》虚拟环境/依赖/Pipenv 等问题解决方法

注:这篇文章的主要受众是《Flask Web 开发实战》的读者。

注2:文中的 $ 符号标识一条命令行命令的开始,$ 前面是当前工作目录,# 号后面是注释。你实际只需要输入 $ 符号和 # 号之间的内容,不包括开头和结尾的空格。

在群聊和论坛里总是看到和虚拟环境和依赖安装相关的各类问题,这篇文章希望能够提供一个统一的解决方案。下次如果遇到有人问虚拟环境/Pipenv/依赖安装相关的问题,请把这篇文章的链接丢过去。

安装 Python 库非常慢?

在进入正题之前,你需要先解决基础设施问题。你在执行 pip install 命令或 pipenv install 等命令时会不会网速非常慢?20k/s 或者干脆看到 Time out,Connection reset 之类的报错,这种情况下,你需要设置 PyPI 镜像。具体操作可以在这篇《从国内的 PyPI 镜像(源)安装 Python 包》看到。

要不要继续使用 Pipenv?

因为书里面在一开始介绍了使用 Pipenv 管理依赖和虚拟环境,同时所有的安装第三方库的命令也都是使用 Pipenv,所以我们要解决的第一个问题是「要不要继续使用 Pipenv?」

我的建议是,如果你在使用的过程中没有遇到过任何报错,那么就继续使用它。直到你觉得它在某些地方不再让你满意。

但是如果你在使用的过程中遇到了问题(首先确保你使用的是最新版本的 Pipenv),比如:

  • 锁定依赖很慢,停留在「locking…」这样的提示不动
  • 执行正确的命令但是总是出现报错

附注 如果你安装依赖时的报错是「MarkupSafe setup.py: ImportError: cannot import name Feature」,请参考这篇文章解决。

那么就继续看下去。

如果不用 Pipenv,我该怎么办?

解决方法和替代工具非常多,这里给出两个。

方法一:不用虚拟环境

最简单的解决方法就是不用虚拟环境。如果你是一个初学者,那么不用虚拟环境完全没问题。现在你把所有的 Python 包全都安装在一个盒子里,你只需要会使用 pip 安装依赖,也就是使用下面的 pip install 命令:

$ pip install flask

附注 顺便说一句,不用虚拟环境时,如果你是使用 Linux 和 macOS 系统的 Python 3 用户,那么执行 Python 和 pip 相关命令的时候需要输入的是 python3 和 pip3,比如:pip3 install flask。后面不再提示。

这个命令会为你安装 Flask。每到需要安装一个包,你就执行这个命令,把上面的 flask 替换成你要安装的包名即可。类似的,书中所有 pipenv install xxx 形式的命令也都替换为 pip install xxx。

这时你可以跳过 Pipenv 和虚拟环境相关的内容。在第一章,当你把当前工作目录切换到 helloflask 文件夹内之后,整个 1.1 小节你只需要执行下面这行命令:

$ pip install -r requirements.txt

而第二部分每章开头的下面这两行命令:

$ pipenv install --dev
$ pipenv shell

都要替换为:

$ pip install -r requirements.txt

这个命令会安装对应项目的所有依赖,所以后续介绍各个 Python 库时的安装命令不需要再执行。

方法二:使用 virtualenv/venv 来管理虚拟环境,搭配 pip 来管理依赖

第二种方法是改用更基础的工具:你仍然使用 pip 安装依赖,同时搭配 virtualenv 或 venv 来管理虚拟环境。如果你选择这个方法,那就跳过书中对 Pipenv 的介绍,改为阅读这篇《要不我们还是用回 virtualenv/venv 和 pip 吧》。阅读完上述文章后,再继续阅读。

现在你可以跳过 Pipenv 相关的内容。第一章切换进 helloflask 目录后,整个 1.1 小节你只需要执行下面的命令:

$ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
$ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
$ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

附注 :上面命令里 # 号及之后的文字是注释,不需要输入。如果你使用 Python2,第一条命令需要改为 virtualenv env。这三行命令的作用依次为:创建虚拟环境、激活虚拟环境、从 requirements.txt 文件安装依赖列表。

类似的,第二部分每章开头的下面这两行命令:

$ pipenv install --dev
$ pipenv shell

都要替换为:

$ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
$ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
$ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

同时书中所有 pipenv install xxx 形式的命令都替换为 pip install xxx(不过你并不需要一个一个执行,因为每章开头执行 pip install -r requirements.txt 时会安装所有项目相关的依赖)。

对于 PyCharm 设置 Python 解释器部分的内容,大致可以沿用,只不过在图 1-4 的位置你需要从列表里选择当前目录 env 文件夹(虚拟环境文件夹)中的 Python 解释器,根据操作系统的不同,将会是 env/bin/python 或 env\Scripts\python.exe。

退出虚拟环境时,使用下面的命令:

$ deactivate

使用书中同样的代码,但是却出现报错?/ 我该怎么安装依赖?

有一些 Python 库在版本变化时会带来 API 的变化,而书中示例程序代码是基于每章开头注明的 Python 库来开发的,所以在更新依赖版本的时候可能会导致示例程序的代码出错。我建议你按照书中的命令来安装依赖,这会从项目依赖文件里安装固定版本的依赖列表,不要跳过第二部分每章开头的命令。

具体来说,如果使用 Pipenv,对应的安装依赖的命令是:

$ pipenv install --dev

如果使用 pip,对应的命令则是(如果创建了虚拟环境,需要先激活虚拟环境):

$ pip install -r requirements.txt

如果是第一章的示例程序,那么在 helloflask 目录下执行一次即可。如果是第二章的四个程序,那么在每一个程序的根目录执行一次即可。后续所有介绍新的 Python 包时给出的安装命令不用再执行。

关于第一部分示例程序的项目结构和启动问题

在第一部分的源码中,一共有 6 个 Flask 程序,分别保存在 helloflask/demos/ 目录下的六个子文件夹内。用来存储环境变量的 .env 和 .flaskenv 文件需要在这些子文件夹内创建,而不是放到顶层目录(helloflask/)。同时为了方便操作,这 6 个程序共用同一个虚拟环境,所以在 helloflask/ 目录下创建虚拟环境。

注意 不要在 helloflask/ 目录下创建 .env 和 .flaskenv 文件,这会导致子目录下的程序无法正确启动(issue #3561)。

如果你按照书里给出的提示执行命令,一般不会出现问题。但是为了防止各种意外情况,这里列一下本书第一部分的操作流程($ 符号前是当前工作目录)。

具体的操作顺序就是,克隆仓库以后,切换到 helloflask 目录:

$ cd helloflask

然后创建虚拟环境、激活虚拟环境、安装依赖:

/helloflask $ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
/helloflask $ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
/helloflask $ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

接着看到介绍 Flask 最小程序部分,启动第一部分的示例程序:

helloflask/ $ cd demos/hello
helloflask/demos/hello $ flask run

现在看到了第二章,启动第二部分的示例程序(先执行 cd .. 回到上层目录,即 demos/ 目录):

helloflask/demos/hello $ cd ..
helloflask/demos $ cd http
helloflask/demos/http $ flask run

看到第三章,启动第三部分的示例程序:

helloflask/demos/http $ cd ..
helloflask/demos $ cd template
helloflask/demos/template $ flask run

以此类推。

遇到了自己没法解决的问题怎么办?

如果遇到问题,你可以先尝试:

  • 看一下本书 GitHub 仓库里的 FAQ 页面 有没有你要找的答案
  • 在搜索引擎搜索你的错误信息关键字,尝试自己解决

自己无法解决的话,可以:

  • 发到 GitHub(issue)和论坛(用文字形式贴出完整的错误信息、相关代码和命令,尽可能的详细描述相关信息)。
  • 发到交流群(建议优先选择发到 GitHub 和论坛,我会定期回复,群聊沟通效率很低,更适合闲聊)