标签归档:Flask Web开发实战

《Flask 从入门到进阶》正式发售

经过五年的写(拖)作(延),我的新书《Flask 从入门到进阶》终于出版了!作为《Flask Web 开发实战》的全新升级版,新版本在原书的基础上进行了大量更新和调整,以便适应当下的技术栈。目前已经有现货可以购买:

至此我在 Flask 这条人生支线的探索也算圆满了。我从大二自学 Python 和 Flask,毕业前开始写作《Flask Web 开发实战》,这本书出版的同时也成为了 Flask 的维护者,并且为大量相关项目贡献代码。从 2018 年出版到现在,一共重印了 14 次,累计售出 2 万本(包括电子书)。收获了很多好评,在豆瓣上评分也保持在 9 分左右:

到现在终于完成了新版本,而且还拿到了 Armin Ronacher(Flask 作者)的推荐语:

Great resources make great developers, and it’s wonderful to see Flask supported by such high-quality education materials.

感谢一直关注这本书并持续催更的朋友!这本书的完成是我们共同努力的结果 :D

本书主要特色

这本书换了一个新书名,出于一个有点滑稽的原因。书名从《Flask Web 开发实战:入门、进阶与原理解析》变成了《Flask 从入门到进阶:Python Web 开发工程化实践》。不过新的副书名也能更好的概括这本书的特点:以学习 Flask Web 开发作为脉络,你能通过这本书掌握相对完整的 Python Web 工程化实践。从 HTTP 原理到数据库 ORM,从单元测试到代码风格再到性能优化,从 Web 程序开发到 Web API 开发,最后再到不同的部署方式。如果想要深入了解框架,最后还有一章源码解析。同时用两个示例程序串起来各种不同的知识点,比如 Flask 项目的组织方式和技巧,以及 Flask 开发中各类功能的实现。

访问这本书的主页查看详细介绍和章节目录:https://helloflask.com/book/4/

新版本主要变化

下面是一些具体内容上的变化:

版本升级

旧版本 新版本
Flask 1.x Flask 3.x
Python 2.7, 3.6+ Python 3.9+
Bootstrap 4.x Bootstrap 5.x
SQLAlchemy 1.x SQLAlchemy 2.x

技术栈更新

旧版本 新版本
Pipenv PDM
Flask-Mail Flask-Mailman
Flake8 Ruff
Open Iconic Bootstrap Icon
Flask-Moment + Moment.js Day.js
jQuery Vanilla JS
AJAX with jQuery Fetch API
Flask for Web API APIFlask
云平台部署 Docker 部署

示例程序上做了比较大的调整,删掉了旧版本的 SayHelloTodoismCatChat,保留两个程序并做了比较大的重构:

旧版本 新版本
Bluelog Greybook
Albumy Moments
helloflask/demos helloflask/examples

新版本计划是想做一些删减,比如从 700 页减到 500 页,毕竟一本厚书还是让人蛮有压力的。虽然删掉了两个章节,但是写到最后又快到了 700 页,实际的厚度比旧版本还要厚一点点。大概不知不觉又加了很多内容进来。

面向的读者群

本书面向的读者群如下:

  • 了解 Python 基本语法,想要自己动手开发网站的编程爱好者
  • 熟悉 Python,想要从事 Python Web 开发的后端工程师、运维工程师和爬虫工程师
  • 想要从 Django 等其他 Python Web 框架转向 Flask 的 Python 工程师

本书不包含对 Python 的基础知识介绍,所以读者需要事先对 Python 基本语法有一定的了解。另外,Web 开发不可避免的会涉及到一些 HTML、CSS 和 JavaScript 知识,如果你不熟悉这些,可以通过 MDN 提供的 Web 开发入门教程学习,简单了解即可。

推荐语

Great resources make great developers, and it’s wonderful to see Flask supported by such high-quality education materials.

—— Armin Ronacher(Flask 作者,Sentry Platform 副总裁)

作为一名对 Python 充满热忱的软件工程师,一名乐于帮助他人、回馈社区的布道者,Grey 对技术准确性、阅读体验感的无限追求都体现在这本新作中,我相信每一名读者都能在字里行间感受到他的诚意。

—— 管俊(《DevOps 实践指南(第 2 版)》译者,戴尔中国卓越研发集团 DevOps 架构师)

这本书内容翔实,推荐给想要系统学习 Flask 的人。不要漏掉书中的小知识点哦。

—— Hsiaoming Yang(Typlog 创始人,Authlib 作者)

关于作者

我是李辉(Grey Li),Flask & APIFlask 的维护者,Flask 开发团队(Pallets Team)成员, HelloFlask 社区和代码厨房社区创建者,微软 Python 方向最有价值专家(MVP)。目前就职于戴尔中国卓越研发集团,担任首席软件工程师。

我热衷于开源软件的应用和推广,为 Flask 等开源项目贡献过大量代码。也曾在 PyCon China、PyCon US、COSCUP、COSCon、CommunityOverCode 等技术大会担任演讲嘉宾。你可以在 GitHub 上看到我的开源项目。

如果你想了解我的新作品或其他动态,可以关注我的 Twitter 和微信公众号,或是加入我的邮件通讯列表

勘误、分享和评价

在阅读过程中如果发现了排版或内容上的错误,欢迎通过下面的方式提交勘误。我会在后续重印时进行修正。感谢反馈!

如果你觉得这本书还不错,欢迎分享到社交网络,或是告诉你的同事和朋友。也欢迎在本书的豆瓣页面标记和评价。

《Flask 从入门到进阶》签名版预售

五年了,终于等到这一天。《Flask Web 开发实战》的全新升级版《Flask 从入门到进阶:Python Web 开发工程化实践》将于下个月正式发售。和上一版一样,先来预售作者签名版。

签名版

签名版的价格和上一版保持一致,依然是 109。付款方式和详细说明可以访问预售页面查看:

https://helloflask.com/book/4/signed/

签名版除了送 2 张 Flask 贴纸,还会额外送一张我的名片,可以用来当书签或者飞着玩。具体发货时间取决于正式发售时间,虽然不能完全保证,但我会尽量让签名书第一批发出。

新书名

你也许好奇为什么要换一个新书名,而不是作为第 2 版出版,即《Flask Web 开发实战(第 2 版)》。我设想了一些合理解释:因为内容变动比较多,换个更贴切的书名;在副书名里加一个 Python 关键字,更容易被别人搜索到……但背后实际的乌龙是:管理书号的工作人员在录入系统时打错了名字,把《Flask Web 开发实战》写成了《Flash Web 开发实战》。

按照出版规定,如果我想作为原书第 2 版来出版,那么新书名就只能叫《Flash Web 开发实战(第 2 版)》了。诸位应该也不想学 Flash Web 吧?

blank

开始写作《Flask Web 开发实战》第二版

Flask Web 开发实战》第二版已经开始写作,计划在今年完成这本书。那本《Python Web API 设计与开发》一直拖着没写完,让我感觉很焦虑。因为拖得太久,HelloFlask 群聊里已经开始谣传我转行送快递了 :D。

所以我想先把《Flask Web 开发实战》新版完成,这样这一年我至少完成了三个年度目标中的一个。「优化」是我擅长和喜欢做的事情,除了要用 Word 写之外,我很乐意给这本书写新版。

内容变动

在各处收集了很多读者反馈和建议,综合我自己的规划,第二版主要有这些大的变动:

  • 不再兼容 Python 2.7,基于 Python 3.x、Flask 2.x。
  • 把大部分知识点集中到第一部分,让第二部分的示例程序内容更精简。
  • 第 1 章使用 pip+venv(+pip-tools) 或 PDM 替代 Pipenv,增加更多对 PyCharm 的介绍。
  • 第 6 章使用 Flask-Mailman 替代 Flask-Mail(同时引入介绍一些新的扩展,比如 Flask-Admin)。
  • 删除第 9、10、11 章(删除后 3 个实战项目),仅保留 Sayhello 和 Bluelog,删减项目的部分功能会合并到这两个项目。
  • 第 10 章删除实战项目,只保留 Web API 开发部分,会新引入 marshmallow 和 APIFlask。
  • 第 12 章添加异步任务相关内容。
  • 第 14 章添加 Docker 相关内容。
  • 删除第 15 章(Flask 扩展开发)。
  • 删掉一些在国内访问有问题的服务介绍,比如 SendGrid(第 6 章)、Heroku(第 14 章)、Twitter 第三方登录(第 11 章)。

大部分内容变动是在做删减,第一版 704 页,第二版内容会删减到 450 页左右。

代码片段

第一部分每一章都有一个示例程序,读者需要切换到每一个子文件夹运行,如果同时自己编写练习代码的话,很容易产生混乱。所以新版的考虑是,第一部分的示例程序不再给出可运行的示例文件(至少不在书里介绍),而是把所有的代码片段放到 HelloFlask 文档,以代码片段的形式组织。读者需要的话可以自己复制代码到本地运行。作为示例,第一章的代码片段可以在这里看到:https://docs.helloflask.com/book/4/snippets/c1/。因为前六章的代码片段变化不大,所以第一版的读者也可以使用这些文档。

P.S. HelloFlask 文档(https://docs.helloflask.com)是我最近刚创建的一个文档集合。所有和书相关的内容都会放到这里,比如勘误、代码片段、衍生文章等。后续也会在这里写一些扩展快速入门介绍、Flask 基础知识等 Flask 相关的东西。

书名

第二版因为有独立的书号,所以可以改书名。责编老师认为原书名就很好,换书名会浪费掉第一本书建立的品牌。我也基本同意这个观点。目前的考虑是主书名不变,出于营销的考虑,副书名或许可以加一个「Python」关键字,比如改成「Python Web 开发入门、进阶与原理解析」。

封面

目前这本书主页上的封面不是正式封面(我用画图软件在初版封面上随手涂了两笔 >_<)。目前的想法是把封面标题颜色换成深红色,然后换一个封面图案,不过暂时还没想好换成什么。

措辞

第一版是用 Word 写的,排版很痛苦,毕竟要在 Word 里面排版代码。第二版也要继续用 Word 写,而且更加痛苦,因为第二版的书稿是编辑老师用工具从 PDF 文件转制成 Word 的,大部分格式都变得很混乱。希望这是我最后一次用 Word 来写技术书。

对于第一版,我交的终稿和最终成书有一些内容的变化,编辑老师会修正错别字和一些病句。但是后面也发现有一些句子修改得并不合理,导致句子原意产生了变化。这次我会在出版前过一遍校正后的书稿,确保不会再出现这样的情况。

另外,编辑老师还会替换一些词汇用法,比如把「我」替换为「笔者」,这个笔者勉强能接受,但是笔者最不能接受的就是要把所有的「点击」改成「单击」。单击一个链接?这太奇怪了。今年和编辑老师再次沟通了这件事,事情终于有了变化,书中的「点击」在这一版不用再死板的按照出版社的规定修改为「单击」。新的读者不会被指导在网页上单击一个链接了。

周边开源项目

我的很多开源项目的开发都是写作驱动的,上一本书驱动我开发了那几个 Flask 示例程序和扩展,这一次同样会有一些开发计划:

  • APIFlask:补齐所有文档,发布 1.0 版本。
  • Bootstrap-Flask:添加 Bootstrap 5 支持。
  • Flask-CKEditor:添加 CKEditor 5 支持,同时集成新版本内置的 Markdown 支持。
  • Flask-WTF:添加多文件上传验证支持。
  • Sayhello:添加分页、多语言、Markdown 支持等功能(总之就是尽可能把删减的示例程序里比较有意思的功能合并到这里)。
  • Bluelog:把 Albumy 的除了图片相关的功能合并过来,会换一个名字(暂定 WeBlog),作为一个社交博客。

如果你对这本书的新版有其他建议和想法,欢迎提出。这两本新书相关的动态会发到 Twitter @helloflask 和我的公众号,欢迎关注。

一个困扰我两年的 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 和论坛,我会定期回复,群聊沟通效率很低,更适合闲聊)

我在一本技术书里放置的十个彩蛋

Flask Web 开发实战》已经出版一年多,书里面的一些彩蛋也不知道有多少被人发现了。再不公开,我可能都要忘了。所以就趁着出版一周年这个契机整理一下吧。

这些彩蛋大都是关于电影的一些双关和文字游戏,有一些说是彩蛋可能会有些勉强。比较正式的一共有 10 个,分别对应十部电影。也就是第三章介绍模板引擎时的实例程序电影清单里列出来的十部电影。

下面按照出现顺序一一剧透。

阿甘正传(Forrest Gump)

P14 第一章:初识 Flask 1.3

这一节介绍运行 Flask 程序,章节标题是「Run, Flask, Run!」。

阿甘正传里,当阿甘被欺负的时候,珍妮总会喊「Run, Forrest, Run!」。

blank

红白蓝三部曲(Three Colours trilogy)

P36 第二章:Flask 和 HTTP 2.2.3.3

介绍 URL 规则的 any 转换器时使用的示例如下:

@app.route('/colors/<any(blue, white, red):color>')
def three_colors(color):
    return '<p>Love is patient and kind. Love is not jealous or boastful or proud or rude.</p>'

这个并没有什么特殊含义,就是列了三个颜色作为 URL 选项,返回的都是圣经里的那句话。

电影清单

P76 第三章:模板

第三章在示例程序里的填充数据是整本书所有彩蛋相关的十部电影,作为一个足够显眼的线索。

movies = [
    {'name': 'My Neighbor Totoro', 'year': '1988'},
    {'name': 'Three Colours trilogy', 'year': '1993'},
    {'name': 'Forrest Gump', 'year': '1994'},
    {'name': 'Perfect Blue', 'year': '1997'},
    {'name': 'The Matrix', 'year': '1999'},
    {'name': 'Memento', 'year': '2000'},
    {'name': 'The Bucket list', 'year': '2007'},
    {'name': 'Black Swan', 'year': '2010'},
    {'name': 'Gone Girl', 'year': '2014'},
    {'name': 'CoCo', 'year': '2017'},
]

另外这个电影清单也有现实版:IMDb镜像豆列

寻梦环游记(CoCo)

P105 第四章:模板

这一章一开始给出了一个 HTML 登录表单的示例,示例代码和渲染后的画面(图 4-1)中,用户名输入框的占位文字是 Héctor Rivera,密码输入框的占位文字是 19001130,勾选了记住登录状态的「记住我(Remember me)」选项。

blank

Héctor Rivera 是电影里小男孩在亡灵世界里遇到的死去的爸爸,他的生日是 1900 年 11 月 30 日,勾选了「Remember me」,他就会被人记住,所以就不会消失了……

消失的爱人(Gone Girl)

P139 第五章:数据库

第一小节介绍关系型数据库的时候给出了一个示例表格(表 5-1),如下:

blank

这两条数据分别对应《消失的爱人》里的男女主角。

记忆碎片(Memento)

同样在第五章,这一章写了一个用来记笔记的示例程序,分别使用了三条笔记内容作为示例演示 CRUD 操作,即:

blank

这三条笔记均是《记忆碎片》里男主角记在各处的笔记。

中国发出的第一封电子邮件

P178 第六章:电子邮件

第六章介绍电子邮件使用的示例邮件正文是「Across the Great Wall we can reach every corner in the world.」。这是 1987 年从中国发出的第一封电子邮件正文。

本来是想把 Wall 换成另一个单词以便反映现状的,但是出于安全考虑,没那么做。不过这句话原版现在来看已经是够讽刺的了。

黑客帝国(The Matrix)

P246 第八章:个人博客

介绍自定义 flash 消息样式的时候,给出了下面的自定义 CSS 类示例:

.alert-matrix {
    color: #66ff66;
    background-color: #000000;
    border-color: #ebccd1; /* 这一行好像没用 */
}

调用示例如下:

flash('Knock, knock, Neo.', 'matrix')

实际的显示效果如下所示:

blank

模拟了电影原图:

blank

 

《未麻的部屋》(Perfect Blue)和《黑天鹅》(Black Swan)

P236 第八章:个人博客

这一章的示例博客名字叫做 Bluelog,模仿了《未麻的部屋》里记录未麻生活的网站,初始化账户的信息如下:

def fake_admin():
    admin = Admin(
        username='admin',
        blog_title='Bluelog',
        blog_sub_title="No, I'm the real thing.",
        name='Mima Kirigoe',
        about='Um, l, Mima Kirigoe, had a fun time as a member of CHAM...'
    )
    ...

另外介绍博客的主题地方提供了蓝色和黑色两个主题,主题名分别是「Perfect Blue」和「Black Swan」,对应《未麻的部屋》和《黑天鹅》两部电影的英文名(后者致敬了前者)。

龙猫(My Neighbor Totoro)

P260 第八章:个人博客

这个算不上彩蛋……在介绍生成英文标题 slug 的 slugify 函数的时候,实际调用的示例使用了龙猫的几种名字作为输入数据:

>>> slugify(u'My Neighbor Totoro')
u'my-neighbor-totoro'
>>> slugify(u'邻家的豆豆龙')
u'lin-jia-de-dou-dou-long'
>>> slugify(u'となりのトトロ')
u'tonarinototoro'

遗愿清单(The Bucket List)

P421 第十章:待办事项程序

这个彩蛋是刻意加的,因为这一章的示例程序是一个 Todo List 程序,所以要找一个和清单相关的电影。示例程序里的几条待办事项就是电影里的一些遗愿。当时还没看过这个电影,特意去看了一遍,感觉一般。

blank

应该就这些了,如果还有其他的,欢迎补充。

那么放置这些彩蛋的目的是?没目的……仅仅就是为了好玩。同时为了能让发现彩蛋的人也会觉得好玩。

 

《Flask Web 开发实战》第二次重印

最近要进行第二次重印,花了整整两天时间整理勘误。接下来的计划是,在第二次重印的书上市前,只在晚上处理论坛问题,尽量不在 IM 上回答和书上错误无关的提问(耗费心力,沟通成本非常高),也不再更新勘误页面。等到第二次重印的书上市,做下面几件事:

  • 写一篇《Flask Web 开发实战 2019 补丁》,汇总所有额外的项目源码变动,第三方工具和库的新版变化,常见的错误和常见问题等。
  • 和豆瓣协商,再为购买旧版本的读者更新一次文件;同时了解微信读书、多看、掌阅、京东、当当几个电子书平台的更新情况。
  • 再集中清理一次盗版文件。
  • 处理 Albumy 现存的两个 bug。
  • 整理一遍所有勘误,更新勘误页面。

2019/4/15 更新:

收到出版社的新版样书,也就是说,1-3 版本的书已经上市销售,目前哪里可以买到还不清楚(估计是京东自营)。

我的书终于重印了

盼了几个月,《Flask Web 开发实战》终于重印了。为什么这么期待重印?当然是因为重印可以修正书中的错误!假如你经常写博客的话,你可以想象一下这样的场景,你在昨天发布的一篇文章里发现大量的错误,但是却没法更新文章,而你的读者还在不停的找出更多的错误……而面对一本已经付印的书,除了感到愧疚,努力整理勘误,告诉读者阅读勘误外,你什么也做不了。这种感觉真是太糟糕了!

这次重印的 1-2 版本主要有下面这些变化:

  • 修正了 1-1 版本 11 月前已知的所有勘误。
  • 更新前言开始介绍 Flask 时的 GitHub 仓库 stars 和 watchers 数量。
  • 前言最后添加了初稿被删掉的后记(稍作修改)。
  • 在前言、附录、封底的作者简介下面添加了勘误页面网址。
  • 完善了多处内容,包括为新版本 Pipenv 和 PyCharm 的变化添加说明,去掉附录中过时的项目等,具体见可改进实现文件
  • 修正了 42 处断行连字符错误。
  • 比 1-1 的封面更好看,封面文字的颜色变淡了,纸张变白了。

尽管修正了这么多的错误,1-2 版本的勘误表仍然还有不少内容。不过,我相信,如果还有下一次重印,那么大部分错误都将得到修正。希望这一天早点来到!

顺便说一句,据出版社编辑的消息,电子书文件已经相应更新,如果没有接收到推送,可以联系各平台客服手动更新文件。

出版社寄了两本 1-2 版本的样书,稍后会在知乎专栏 Hello, Flask! 送出,也算是稍稍填补一下我的愧疚。

写作一本技术书,能给一个社区带来哪些改变?

写作《Flask Web开发实战》花费了一年多的时间,在这期间,除了编写5个项目实例和写作外,我还花了一部分时间来和书中涉及的Python库(主要是Flask扩展)打交道。这篇文章总结了这本书的写作给整个Flask社区带来的一些改变。

国内第一本Flask书

虽然国内已经有几本书介绍过Flask,但都是顺便介绍,而不是真正意义上的Flask书。因此,这本书可以说是国内第一本Flask书,很高兴能为国内的Flask社区带来这样一本书。希望这本书能够推动更多人了解和使用Flask,进而为社区贡献更多的力量。

完善多个开源项目

在实际介绍和使用某个库时,总会遇到各种各样的问题,主要有两类:

  • Bug
  • 不完善的用法或实现

Bug不用多说,肯定要处理。对于不完善的用法和实现,如果沿用原来的代码,一来不容易读者理解,二来还需要在书中加进很多不必要的内容。所以,我更倾向于让这些代码变得更好一点。

如果把不完善的实现写到书里,我会觉得非常不舒服。比如说,WTForms本身是支持设置内置的错误消息的语言的,我们可以设为中文,而不用手动写错误消息。但是在Flask-WTF中,因为内部重写了相关方法,导致没法在不使用Flask-Babel的情况下使用内置的翻译。问题来了:如果在第4章介绍Flask-Babel和i18n等相关概念的话,这显然需要占据大量篇幅,并不合适,而且这部分内容已经计划加入到第10章。

为此,书的安排没法改动,那么只好调整Flask-WTF的内部实现,所以就有了这个PR。类似的情况还有很多,比如Firefox支持headless模式后,Selenium中却没有提供和Chrome相同的导入接口;Flask-OAuthlib中,传入access令牌只能使用元组或字典类型,而不能使用更直观的字符串变量传入……

下面是一些写作期间提交的开源贡献:

当然,如果加上其他没有太大意义的文档更新,这个列表还可以更长。
 
值得特别说明的是Werkzeug的这个PR,这个PR对应的bug困扰了我很长一段时间。一开始我把问题归咎到python-dotenv身上,还创建了一个PR(#101),但是维护者迟迟没有回复。后来再次研究,发现可以直接在Werkzeug内部解决,于是创建了#1320,然后被lepture用更好的实现取代了(#1321)。不过,在python-dotenv创建的那个PR倒是促成了Pipenv的这个PR(#2386)。

推动发布新的版本

除了让PR被合并,还要让这些项目发布新的release,这样才能在项目源码公开后,让读者可以正常使用。在我的推动下,Flask-OAuthlib、Flask-Whooshee和Flask-Moment都发布了新的版本。

其他的项目,除了Werkzeug以外,大部分都在整个写作的时间跨度发布了新的版本。尤其重要的是Flask的1.0版本。从书一开始写作,我就直接采用了主分支的代码,书中我直接将版本号写为0.13。如果Flask迟迟不发布新版本,那我会陷入一个很尴尬的境地:要么大胆采用主分支代码,但是可能会出现变动,导致书中的内容不可靠;要么采用旧版本,那书出版后可能会很快面临过时的危险。还好,在写作接近尾声的4月末,新版本1.0发布了。

4个Flask扩展

用于集成Bootstrap的扩展Flask-Bootstrap目前的维护状态很糟糕:上一次release是17年1月,上一次合并commit是17年3月,而且已经很长时间不再处理PR和Issue。

原项目还存在很多问题:Jinja语法不标准、内置基模板引入不必要的复杂度、分页宏不支持传入URL片段值。除此之外,让我决定写新的扩展替代它的最主要原因是因为它不支持Bootstrap 4,而我不能接受在书中引入一个这样过时的项目。

为了解决这些问题,只好写了Bootstrap-Flask。最终决定写替代扩展的时候,书已经完成了三分之二,大量旧的内容和源码都要改写。把书稿和源码从Flask-Bootstrap和Bootstrap3迁移到Bootstrap-Flask和Bootstrap4是一个非常痛苦的过程,到现在还记忆犹新……

对于某些空白的领域,我写了三个扩展来简化集成操作:

5个Flask开源项目

在此之前,完善的Flask开源项目屈指可数,除了Flask官方提供的两个示例程序外,就只有Miguel Grinberg的两个程序。而这本书带来了5个相对比较完善、所有依赖都基于最新版本的开源项目(严格来说,第1个程序比较简单,可以排除在外):

如何向Jinja宏传递额外参数(*args和**kwargs)?

这段时间有多个读者问关于Jinja宏定义时的参数接受问题。这一点在《Flask Web开发实战》里没有介绍,这篇文章作为一个补充。

一个不符合直觉的设定

在某个晴朗的早晨,你打开电脑,想在你的项目Jinja模板里编写一个宏来简化操作。按照直觉,你可能会像定义Python函数那样来定义宏,传入**kwargs来让它接收任意数量的关键字参数,比如:

{% macro say_hello(**kwargs) %}
     ...
{% endmacro %}

或是传入*args让它接收任意数量的位置参数:

{% macro say_hello(*args) %}
     ...
{% endmacro %}

遗憾的是,上面的调用会分别获得下面的错误信息:

jinja2.exceptions.TemplateSyntaxError: expected token 'name', got '**'
jinja2.exceptions.TemplateSyntaxError: expected token 'name', got '*'

在Jinja宏里接收额外的关键字参数和位置参数

在Jinja中,宏默认会自动接收额外的关键字参数和位置参数,并在宏内部提供kwargsvarargs特殊变量来获取它们。具体来说,在定义宏的时候,不需要进行任何声明。在宏的内部,你可以直接使用kwargs字典来获取额外的关键字参数;同样的,你可以使用varargs元组来获取额外传入的位置参数。

下面是使用kwargs的示例:

{% macro say_hello() %}
    <p>Hello, {{ kwargs['name'] }}!</p>
{% endmacro %}

{# 调用示例 #}
{{ say_hello(name='Grey')}}

你可以把这个字典传递给其他函数,比如url_for():

{% macro nav_link(endpoint, text) %}
    <a href="{{ url_for(endpoint, **kwargs) }}">{{ text }}</a>
{% endmacro %} 

{# 调用示例 #}
{{ nav_link('index', 'Home', foo='value1', bar='value2')}}

下面是使用varargs的示例:

{% macro say_hello() %}
    <p>Hello, {{ varargs[0] }}!</p>
{% endmacro %}

{# 调用示例 #}
{{ say_hello('Grey')}}

提示 在宏内部,如果kwargs字典里没有对应的键,那么会返回空字符串,而不是抛出KeyError异常;如果向varargs元组索引一个超出范围的下标值,也会返回空值,而不会抛出IndexError异常。

隐藏的陷阱

虽然宏自动处理额外传入的关键字参数和位置参数,但是这里有一个隐藏的小陷阱。如果你在调用一个宏的时候传入了额外的关键字参数和位置参数,但是宏的内部并没有使用它们,这时就会出错。比如下面使用kwargs的示例:

{% macro say_hello() %}
    <p>Hello!</p>
{% endmacro %} 

{# 调用示例 #} 
{{ say_hello(name='Grey')}}

调用宏的时候传入了name参数,但是宏内部并没有使用它,这时Jinja会抛出下面的异常:

TypeError: macro 'say_hello' takes no keyword argument 'name'

类似的是位置参数,你会获得下面的异常:

TypeError: macro 'say_hello' takes not more than 1 argument(s)

所以,如果你想让某个宏接收额外的关键字参数或位置参数,你就分别需要在这个宏内部至少调用一次(access)kwargs字典或是varargs元组。一般情况下,你并不需要担心这个问题。

本文隶属于《Flask Web开发实战》番外文章系列。

《Flask Web开发实战》的封面设计是如何诞生的?

一本书当然包含很多故事,这篇文章来介绍一下《Flask Web开发实战》封面设计的过程。

其实,这也算不上设计。因为这本书隶属于“Web开发系列”,这个系列的书都有一个通用的设计模板,唯一不同的就是确定封面中的图案。在选择图片前,我立下了几个规则:

  • 使用Flask风格图片(黑白线条)
  • 突出“实战”的含义

基于这两点,我最先发现的合适素材是一张飞机的图片,这个图片是Flask作者Armin Ronacher在2011年的演讲“Opening the Flask”中使用的幻灯片的第10页的插图:

blank

这就是封面设计初稿:

blank

我非常喜欢这个封面,编辑也表示很不错。遗憾的是,我没有搜索到这个图片的版权方,无法商用,而且Armin Ronacher也表示记不起来是从哪里拿到这张图片。

接下来,我花费了大量时间想找一个替代品,但是却始终找不到满意的。

无奈之下,我只好扩大范围,不再局限于飞机图片,而且延伸到了船、武器、人物,陆续找到下面这些替代品:

blank

也许是因为“曾经沧海”,试来试去这些都不满意。这时我忽然有了一个念头:Miguel Grinberg那本《Flask Web开发》封面上是一只狗,我可以用一只狼呀。既能突出实战的特点,又有一点遥相呼应的味道,形成一个有趣的对比。

虽然心底有一些犹疑:会不会太像O’reilly的“动物书”?读者会不会觉得这是山寨?但是当我看到这张狼的图片时,顿时就喜欢上了,疑虑也消了大半:

blank

把图片放到封面上看起来也非常合适:

blank

这就是最终的封面初稿。当然,经过出版社设计人员的美化,最终的封面要好看得多:

blank

于是,《Flask Web开发实战》的封面就这么诞生了。