搬家有益健康

人没事就该多搬搬家,搬家的好处实在太多了。随便列几条:

  • 锻炼肱二头肌、肱三头肌、胸大肌、斜方肌……
  • 清楚自己买了多少没用的东西,有多少书买来没有看,把这些东西搬来搬去可以惩罚自己,顺便反省自己的消费习惯。
  • 从房租的变化来认识到自己到底有多穷。
  • 换个新环境,给生活增加新鲜感。
  • 结识新房东和新中介 ==!

COSCUP 2019:如何在两年内从初学者成长为流行开源项目维护者和技术书作者?

这是我在 COSCUP 2019 第一天下午的演讲,主要介绍了这两年的经历和一些经验,內容偏娱乐向(本来想说内容很水,但又想到在台湾「很水」就是「很强」的意思……)。有时间会写一篇文章记录参加 COSCUP 的经历和感想。

相关资源

对于录像内容的一些备注:

  • 前两分钟话筒坏了,没声音 :/
  • 《这个杀手不太冷》的英文原名是「The Professional」,即「专业人士」。
  • 幻灯片里出现的几个电影截图分别是《这个杀手不太冷》、《热血警探》、《回魂夜》和《将军号》。
  • 幻灯片里的两张「开源」照片,一张收集自 COSCUP 的 Telegram 群组,一张拍于徐州家乡小镇。
  • 某一张幻灯片多了「重要」两个字。
  • 漏掉一个关键想法没说:除了做出有趣和系统的作品外,还要做比上一个作品更大更好的作品(直到你造出飞船,当上总统,或是实现你的终极目标)。

演讲介绍

2016 年,我开始学习 Flask,并尝试为多个开源项目贡献代码。两年后,我加入 Flask 开发团队,并出版国内第一本 Flask 书《Flask Web 开发实战》。这中间发生了什么?除了运气的帮助,我做对了哪些事情,经验是否能被复制?其中大量的开源贡献起到了什么样的作用?在本议程里,我会从我的自身经历出发,在编程学习、个人成长、开源贡献、技术写作等方面分享一些经验和思考。如果你想高效学习一门新技术、参与开源贡献、建立个人品牌、或是写作一本技术书,这些信息或许能够给你带来帮助。

演讲总结

总体效果还不错,现场氛围很好,有很多人喜欢。另外因为这是第一次拿到完整的演讲录像,自己看了一遍,总结了一些问题:

  • 说太多「然后」
  • 太紧张,漏掉一些内容
  • 表情和肢体太多小动作
  • 正前方的某一个观众太严肃,而视线又总是落到他身上……

感谢 COSCUP 的 Bob Chao 帮忙协调提前拿到录像,感谢开源社的刘天栋老师帮我找到李思颖老师,感谢李思颖老师帮我找到 Charon,感谢 Charon 帮忙处理视频。​不过我最后还是用了自己剪辑的那一版视频(嘘)。

PyCon China 2019 闪电演讲

什么是闪电演讲? 

闪电演讲(lightning talk)是指 5~10 分钟长度的快速演讲。通常会将多个闪电演讲安排在一起,有时也被称作数据突袭(data blitz)。

PyCon 闪电演讲会是什么样子?

在我的推动下,今年的 PyCon China 正式引入了闪电演讲环节,将会作为大会的特殊项目放到最后进行,而且在时间上是独占的。所有的闪电演讲都会控制在 5 分钟左右,并且和 Python 相关。上海场的闪电演讲会放到分会场 B(Web 专场)进行,下午四点四十分开始,一定记得来参加哦!

你会听到哪些内容?

今年 PyCon China 的闪电演讲安排如下:

上海 9.21

  • 【Python 虚拟环境和依赖管理工具大乱斗】李辉 – 独立 Web 开发者,Flask 维护者(之一)
  • 【500 行 Python 写一个渲染器】谭啸 – 蚂蚁金服开发工程师
  • 【基于 OwlReady2 的人机交互】宋从威 – 浙江工业大学之江学院讲师
  • 【Byte Code 的革命】赵俊德 – 西安德新软件创始人
  • 【使用 Sphinx 制作 Web 文档】陈照强 – 中科院上海药物所高级研究员
  • 【一键将 C/C++ 代码转换为 Python 能调用的代码】韦泽华 – 上海韦纳科技有限公司

北京 10.19

  • 【图算法你好:反欺诈应用介绍与实作】-黄少瑄 – 清华大学/平安科技实习
  • 【Python 与视效行业】郭李灼 – 北京微帧数字科技有限公司
  • 【Requests 长链接与 TIME_WAIT】- 刘玉龙 – 高瓴资本百丽国际后端工程师
  • 【Python 与 Redis 的一二事】庞若然 – 爱奇艺

深圳 10.20

  • 【Python C 拓展在各平台的打包与发布】赵丰 – 清华大学深圳研究生院博士生
  • 【为 Python Function 自动生成 Web UI】彭未康 – 广州爱范儿科技股份有限公司

成都 10.26

  • 【一次关于 SQLAlchemy session 的项目排错经验分享】吴强 – 新希望金融科技有限公司后台开发工程师
  • 【Python在区块链领域中的地位?】鄢倩 – ThoughtWorks

理想情况下,闪电演讲的最大的两个特点就是简短和有趣。这篇文章先让你感受一下“简短”的力量,“有趣”就留到大会下午来实际体验吧。

要不我们还是用回 virtualenv/venv 和 pip 吧

这篇文章没什么新东西,只是介绍古老又靠谱的 Python 虚拟环境和依赖管理方式:virtualenv/venv+pip。一来方便被我在《Flask 入门教程》和《Flask Web 开发实战》带入 Pipenv 坑的初学者了解基础工具的用法,二来方便其他 Python 初学者参考,自己顺便做个总结。如果你想了解更多详细内容,Python 官方教程这一章写的更好,可以替代这篇文章。

既然大部分试图简化 Python 虚拟环境和依赖管理工作流程的新工具都不够稳定,继续使用 virtualenv/venv 和 pip 这样的底层工具也是一个不错的选择,而且大多数人也是这么做的。它们虽然用起来有一点麻烦,但至少更可靠。

基本概念

下面是一些基本概念:

  • PyPA:指 Python Packaging Authority,一个维护 Python 打包相关项目的小组,相关项目具体见 https://github.com/pypa
  • pip:Python 包安装器 。
  • virtualenv:Python 虚拟环境管理工具。
  • venv:Python 标准库内置的虚拟环境管理工具,Python 3.3 加入,Python 3.5 开始作为管理虚拟环境的推荐工具,用法类似 virtualenv。如果你使用 Python 3,推荐使用 venv 来替代 virtualenv。

使用 virtualenv/venv 管理虚拟环境

venv 模块

如果你使用 Python 3(具体说是 Python 3.3 及以上版本),推荐使用标准库内置的 venv 模块替代 virtualenv,两者的使用方式基本相同,唯一不同的是创建虚拟环境的方式。

如果你使用 Python 2,那就只能选择 virtualenv,你需要额外安装它。我先假设你已经安装了 pip,因为在 Python 2 >=2.7.9 或 Python 3 >=3.4 这些版本的 Python 会一并安装 pip,其他版本可以参考文档的安装部分。在 Windows 下使用下面的命令安装 virtualenv:

$ pip install virtualenv

其他操作系统可以使用下面的命令安装:

$ sudo pip install virtualenv

尽管不推荐使用 sudo pip 的方式安装 Python 包,但这仍然是最简单和统一的方式。更安全的方式是使用系统包管理器来安装,或是使用 pip –user 方式安装。

创建虚拟环境

假设我们的项目名叫 snow,创建对应的文件夹然后切换到根目录:

$ mkdir snow
$ cd snow

如果使用 venv,那么使用下面的命令创建虚拟环境,其中 snow-venv 是虚拟环境的名字,也作为创建的虚拟环境文件夹名称,你可以自由修改(通常会使用 venv 或 env 作为虚拟环境名):

$ python -m venv snow-venv

如果使用 virtualenv,则使用下面的命令:

$ virtualenv snow-venv

这会在当前目录创建名为 snow-venv 的虚拟环境文件夹,你需要把这个文件夹名称加入 .gitignore 文件以便让 Git 忽略。

激活虚拟环境

通过执行对应的激活脚本来激活虚拟环境,不同操作系统的激活命令(激活脚本及路径)有一点不同。Windows(CMD.exe)使用下面的命令激活:

$ snow-venv\scripts\activate

Linux 和 macOS(bash/zsh)使用下面的命令:

$ source snow-venv/bin/activate

或:

$ . snow-venv/bin/activate

类似的,其他终端程序可以执行对应的激活脚本来激活虚拟环境。

激活虚拟环境以后,命令行提示符前会显示当前虚拟环境的名字:

(snow-venv) $

使用 deactivate 命令可以退出虚拟环境。

使用 pip 管理依赖

简单列一下基本用法,虽然大部分人都很熟悉了……以 Flask 为例,首先是安装依赖:

(snow-venv) $ pip install flask

更新依赖:

(snow-venv) $ pip install --upgrade flask

或是:

(snow-venv) $ pip install -U flask

卸载依赖:

(snow-venv) $ pip uninstall flask

除此之外,还有 pip show flask 命令可以查看某个依赖的详细信息,pip list 列出所有依赖。

下面的命令可以手动生成依赖列表:

(snow-venv) $ pip freeze > requirements.txt

如果你需要手动开发依赖和生产依赖,可以手动把开发相关的依赖放到单独的文件,比如 requirements-dev.txt。

当你需要在新的机器创建程序运行环境时,(创建虚拟环境后)只需要使用下面的命令从依赖文件安装所有依赖:

(snow-venv) $ pip install -r requirements.txt

如果安装包的时候速度太慢,可以考虑设置 PyPI 国内镜像,具体参考这篇文章

从其他工具迁移回来

如果你想从 Pipenv 迁移回来,方法很简单,只需要生成一个 requirements.txt 文件即可。使用下面的命令生成一般依赖列表:

$ pipenv lock -r

使用下面的命令输出开发依赖列表:

$ pipenv lock -r --dev

然后手动把两个命令的输出保存为 requirements.txt 和 requirements-dev.txt。

从 Poetry、Conda 等其他工具迁移回来可以使用 pipreqs 来生成 requirements.txt,它会基于项目代码的导入语句来生成依赖列表。

在下一篇文章,我会介绍一些辅助工具来搭配 virtualenv/venv+pip 使用,让虚拟环境和依赖管理更方便,比如 virtualenvwrapper、pip-tools 等。

(4)

只用来管理 Python 版本和虚拟环境,Miniconda 也是一个很好的选择

Anaconda 是一个面向数据科学的 Python 发行版,它打包了 Conda、Python 和一堆机器学习和人工智能相关和常用的包,而且还可以用来安装一些非 Python 编写的库。对于科学计算相关的用途非常方便,开箱即用。看起来很适合实验室或是学校机房使用……

对于常规的 Python 开发来说,Anaconda 太重了,而且主要面向科学计算领域,但是精简版的 Miniconda 却是一个很好的 Python 版本和虚拟环境管理工具。

更重要的是 Miniconda 兼容三个主流操作系统,而且不同平台使用同样的命令和接口(Conda 4.6 以上)。这大概算是个人偏好,因为我总在写东西给别人看,所以总是喜欢兼容主流操作系统并且接口统一的解决方案。而且另一方面 pyenv、virtualenvwrapper、direnv 等等相关替代工具都没有原生 Windows 支持。 

Miniconda 是什么

先来理清几个概念:

  • Conda:包、依赖和环境管理器。
  • Anaconda(某种蟒蛇的名字):面向数据科学的 Python 发行版,包含 conda、conda-build、Python 和 100+ 常用的数据科学常用的库及其依赖。
  • Miniconda:精简版的 Anaconda,也是一个 Python 发行版,只包含 conda、Python 和一些基本的包。

相关资源:

Miniconda 基本用法

首先你需要访问下载页面下载对应操作系统的安装包进行安装,Python 版本选择你想作为默认选项(base version)的版本。然后打开 Windows 下的 Anaconda Prompt 或是 Anaconda Powershell Prompt,Linux 或 macOS 直接使用终端程序。

下面是一些关键用法介绍,详细内容可以阅读 Conda 文档

创建虚拟环境

使用 conda create 命令创建虚拟环境,使用 –name 选项(-n)指定虚拟环境名称:

$ conda create --name foo

使用 conda info 命令查看当前环境名称、Python 版本、虚拟环境文件夹位置、Conda 版本等等各种信息:

$ conda info

使用 –envs 选项(-e)查看所有已创建的虚拟环境。在列出的虚拟环境中,使用星号(*)标识的是当前激活的虚拟环境:

$ conda info --envs

激活虚拟环境

使用 conda activate 命令激活虚拟环境,添加虚拟环境名作为参数:

$ conda activate foo

激活以后会在命令行提示器前显示虚拟环境名称,比如:

(foo) $

不添加虚拟环境名称,就会重新激活基础环境(base):

$ conda activate

设置虚拟环境的 Python 版本

在创建虚拟环境的时候,可以使用 python 参数指定 Python 版本。假设你使用的 Miniconda 默认版本是 Python 3.7,如果你想创建一个 Python 2.7 的虚拟环境,使用下面的命令:

$ conda create --name snakes python=2.7

你可以在虚拟环境名加上标识方便识别 Python 版本,比如:

$ conda create --name snakes-py27 python=2.7

搭配 pip / Poetry 来管理依赖

因为 Conda 安装库的时候默认使用 Conda 自己的仓库,这里包含的 720 多个库除了流行的 Python 包外大多是数据科学相关的包。所以,更好的选择是使用官方 PyPI 仓库,这样可以确保你使用到最近更新的包,而且不会出现有些包找不到的情况。

我们要做的就是只用 Conda 的 Python 版本和虚拟环境管理功能,不用它来管理依赖。依赖管理(安装、卸载、更新等)仍然使用 pip 进行,或是进一步搭配 pip-tools 来管理依赖。

你需要在 conda 环境内使用下面的命令安装 pip:

$ conda install pip

或是统一使用下面的命令格式创建虚拟环境:

$ conda create --name bar pip

这样在执行 pip 命令时会使用虚拟环境内的 pip,而不是系统全局的 pip。这样做的副作用是会产生几个多余的依赖。

除此之外,你也可以搭配使用 Poetry。Poetry 默认会自动创建虚拟环境,所以需要关闭 Poetry 自动创建虚拟环境的设定,执行下面的命令即可:

$ poetry config settings.virtualenvs.create false

总结

按照我目前掌握到的信息,Miniconda 的优缺点总结如下:

优点

  • 用法简单,​易于上手
  • 替代 pyenv+virtualenv/venv+virtualenvwrapper 的多个工具组合
  • 类似 virtualenvwrapper,可以在任意位置激活虚拟环境,而不是必须在项目根目录
  • 支持管理不同的 Python 版本
  • 兼容性很好,支持 Windows

缺点

  • 在 Windows 下,需要使用专用的命令行程序
  • 依赖管理比较弱,需要搭配 pip/Poetry 来使用
  • conda 和 pip/Poetry 组合存在潜在的冲突,但情况在改善

既然一个 Miniconda 就能很好的胜任 Python 版本管理和虚拟环境管理的任务,为什么要用 pyenv+virtualenv/venv+virtualenvwrapper 呢?遗憾的就是依赖管理功能不够完善,和 pip/Poetry 搭配使用则可能会有潜在的冲突。所以,推荐觉得 virtualenv/venv+pip 搭配太麻烦的初学者使用;推荐能接受 Conda+pip/Poetry 这种搭配的人使用;推荐使用 Python 做数据科学相关工作的人使用。

对比之下,最稳定的解决方案大概还是 virtualenv/venv+pip+其他工具……
(如果你发现了错误描述、有不同看法和想要补充的信息,欢迎评论。)

(3)

相比 Pipenv,Poetry 是一个更好的选择

前情提要

Pipenv 描绘了一个美梦,让我们以为 Python 也有了其他语言那样完善的包管理器,不过这一切却在后来者 Poetry 这里得到了更好的实现。

这几年 Pipenv 收获了很多用户,但是也暴露了很多问题。虽然 Lock 太慢、Windows 支持不好和 bug 太多的问题都已经改进了很多,但对我来说,仍然不能接受随时更新锁定依赖的设定,在上一篇文章《不要用 Pipenv》里也吐槽了很多相关的问题。

在这篇文章里,我会介绍一个看起来和事实上都更靠谱的 Python 虚拟环境和依赖管理工具 Poetry,作者是 Sébastien Eustace。这是一个新的坑吗?我想并不是,尽管这是一个更年轻的工具,1.0 还没有发布,也存在各种各样的 bug,但至少基本使用流程没有问题,用法设计也符合直觉。

Poetry 是什么

Poetry 和 Pipenv 类似,是一个 Python 虚拟环境和依赖管理工具,另外它还提供了包管理功能,比如打包和发布。你可以把它看做是 Pipenv 和 Flit 这些工具的超集。它可以让你用 Poetry 来同时管理 Python 库和 Python 程序。

安装 Poetry

官方推荐的安装命令是使用自带的 get-poetry.py 脚本,使用 curl:

$ curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python

或者直接下载这个安装脚本 get-poetry.py,然后在本地执行。

因为这个命令在安装时会从 GitHub 下载一个 7M 的压缩包,如果不用代理某些地区可能会很慢。实际测试使用代理安装耗时约 30 秒,不用代理等了 5 分钟,然后连接被重置。

如果没有用代理,可以用 pip 安装(不过 Poetry 官方文档不建议这么做,因为有可能会造成依赖冲突,可以考虑用 pipxpipsi):

$ pip install --user poetry

安装后可以使用下面的命令确认安装成功:

$ poetry --version
Poetry 0.12.17

如果报错,可以试试重新创建一个命令行会话。

附注 在 Mac 上安装报错 ~/.bash_profile 权限错误,临时没管,也能正常运行。后续可以考虑手动把 ~/.poetry/bin 加进去。

Poetry 的基本用法

Poetry 的用法很简单,大部分命令和 Pipenv 接近。我们需要先了解一些基本概念和 Tips:

  • 使用 PEP 518 引入的新标准 pyproject.toml 文件管理依赖列表和项目的各种 meta 信息,用来替代 Pipfile、requirements.txt、setup.py、setup.cfg、MANIFEST.in 等等各种配置文件。
  • 依赖分为两种,普通依赖(生产环境)和开发依赖。
  • 安装某个包,会在 pyproject.toml 文件中默认使用 upper bound(中文翻译?)版本限定,比如 Flask^1.1。这被叫做 Caret requirements(中文翻译?),比如某个依赖的版本限定是 ^2.9.0,当你执行 poetry update 的时候,它或许会更新到 2.14.0,但不会更新到 3.0.0;假如固定的版本是 ^0.1.11,它可能会更新到 0.1.19,但不会更新到 0.2.0。总之,在更新依赖的时候不会修改最左边非零的数字号版本(对于 SemVer 版本号而言),这样的默认设定可以确保你在更新依赖的时候不会更新到具有不兼容变动的版本。另外也支持更多依赖版本限定符号
  • 不会像 Pipenv 那样随时更新你的锁定依赖版本,锁定依赖存储在 poetry.lock 文件里(这个文件会自动生成)。所以,记得把你的 poetry.lock 文件纳入版本控制。
  • 执行 poetry 或 poetry list 命令查看所有可用的命令。

如果你想了解更多进阶的内容,比如设置命令行补全、打包和发布等等,请阅读 Poetry 文档

准备工作

如果你是在一个已有的项目里使用 Poetry,你只需要执行 poetry init 命令来创建一个 pyproject.toml 文件:

$ poetry init

根据它的提示输入你的项目信息,不确定的内容就按下 Enter 使用默认值,后续也可以手动更新。指定依赖的环节可以跳过,手动安装会更高效一点。

如果你想创建一个新的 Python 项目,使用 poetry new <文件夹名称> 命令可以创建一个项目模板:

$ poetry new foo

这会创建一个这样的项目结构:

foo
├── pyproject.toml
├── README.rst
├── foo
│   └── __init__.py
└── tests
    ├── __init__.py
    └── test_foo.py

如果你想使用 src 文件夹,可以添加 –src 选项,这会把程序包嵌套在 src 文件夹里。

创建虚拟环境

使用 poetry install 命令创建虚拟环境(确保当前目录有 pyproject.toml 文件):

$ poetry install

这个命令会读取 pyproject.toml 中的所有依赖(包括开发依赖)并安装,如果不想安装开发依赖,可以附加 –no-dev 选项。如果项目根目录有 poetry.lock 文件,会安装这个文件中列出的锁定版本的依赖。如果执行 add/remove 命令的时候没有检测到虚拟环境,也会为当前目录自动创建虚拟环境。

激活虚拟环境

执行 poetry 开头的命令并不需要激活虚拟环境,因为它会自动检测到当前虚拟环境。如果你想快速在当前目录对应的虚拟环境中执行命令,可以使用 poetry run <你的命令> 命令,比如:

$ poetry run python app.py

如果你想显式的激活虚拟环境,使用 poetry shell 命令:

$ poetry shell

安装包

使用 poetry add 命令来安装一个包:

$ poetry add flask

添加 –dev 参数可以指定为开发依赖:

$ poetry add pytest --dev

追踪 & 更新包

使用 poetry show 命令可以查看所有安装的依赖(可以传递包名称作为参数查看具体某个包的信息):

$ poetry show

添加 –tree 选项可以查看依赖关系:

$ poetry show --tree

添加 –outdated 可以查看可以更新的依赖:

$ poetry show --outdated

执行 poetry update 命令可以更新所有锁定版本的依赖:

$ poetry update

如果你想更新某个指定的依赖,传递包名作为参数:

$ poetry update foo

卸载包

使用 poetry remove <包名称> 卸载一个包:

$ poetry remove foo

常用配置

Poetry 的配置存储在单独的文件中,比 Pipenv 设置环境变量的方式要方便一点。配置通过 poetry config 命令设置,比如下面的命令可以写入 PyPI 的账号密码信息:

$ poetry config http-basic.pypi username password

下面的命令设置在项目内创建虚拟环境文件夹:

$ poetry config settings.virtualenvs.in-project true

另一个常用的配置是设置 PyPI 镜像源,以使用豆瓣提供的 PyPI 镜像源为例,你需要在 pyproject.toml 文件里加入这部分内容:

[[tool.poetry.source]]
name = "douban"
url = "https://pypi.doubanio.com/simple/"

不过经过测试 Poetry 会使用 pip.ini 设置的 PyPI 镜像,而且豆瓣的源好像很久没更新了(创建虚拟环境安装的默认依赖里 importlib-metadata==0.20 找不到),这篇文章列出了一些其他国内的 PyPI 源。

总结

总的来说,我愿意深入尝试和使用 Poetry。当然,经过使用 Pipenv 的痛苦经历,我对推荐工具这种事情变得更保守了。所以我不推荐 Python 初学者使用,不推荐直接在生产环境使用,不推荐没法正常访问国际互联网的人使用。

列一些我了解到的优缺点:

优点

  • 使用标准的 pyproject.toml 标准,不用写多个配置文件
  • 同时支持管理 Python 程序和 Python 库
  • 更符合直觉的默认设计,比如不会随便更新锁定版本的依赖
  • 干净简洁的命令行输出,没有星星和蛋糕
  • 安装包的时候,使用 upper bound 版本限定,而不是 Pipenv 默认的通配符
  • 卸载包的时候,直接卸载孤立的子依赖,不需要像 Pipenv 那样需要再执行 pipenv clean

缺点

  • 「poetry」这个单词有一点难打……
  • 引入新的 pyproject.toml 标准,旧项目需要一点迁移成本和学习成本
  • 会有一些潜在的 bug
  • 解析依赖的过程有时候会久一点
  • 对虚拟环境的管理控制有些弱,没有 Pipenv 那样的删除虚拟环境和清空依赖的操作
  • 缺少一个稳定的维护团队,有大量 issue 和 PR 等待处理,但情况在逐渐好转

当然,你还是可以选择继续使用 virtualenv 和 pip 这些基础工具,直到有一个完美的解决方案出现。或者,也可以选择试试新东西,然后尝试改进它,让完美的解决方案早一点出现。

(2)

回应《也谈「不要用 Pipenv」》

看了董伟明老师(@董伟明)的《也谈「不要用 Pipenv」》,这篇文章对其中的一些观点做出一些回应和解释。

也看了 Frost Ming 老师(@岂不美哉)的《Pipenv 有什么问题》,很感谢他做出的努力,祝 Pipenv 早日脱离 Kenneth Retiz 的影响,越来越好。

(Kenneth Retiz 下文简称 KR)

KR 有没有借 PyPA 之名来做背书?

所以作者并没有想着用来背书。

我仍然认为 KR 有利用 PyPA 做背书,甚至在误导别人 Pipenv 是 Python 官方(混淆 PyPA 和 Python 官方的概念)推荐的工具。

证据 1

2017 年 8 月 29 号,在 Pipenv 还不够成熟的时候,PyPA 成员 Thea Flowers 创建了一个 PR 要把 Pipenv 添加到 PyPA 的打包教程里,介绍使用 Pipenv 安装和管理依赖(注意这时候 Pipenv 根据教程的内容在 Windows 上是没法正常使用的,具体见 9 月 2 号的这个 issue)。

2017 年 8 月 31 号,Thea Flowers 自己合并了 PR。注意这个教程页面是临时的单独页面,还没有正式放到打包教程的页面里。

2017 年 9 月 1 号,KR 在 Pipenv 的 README 里加了这样一行介绍语:「Pipenv — the officially recommended Python packaging tool from Python.org, free (as in freedom).」(commit

其中的关键内容翻译过来大概是「官方推荐的 Python 打包工具,来自 Python.org」。

仅仅因为在 PyPA 的打包文档里加入了一个短教程(其中介绍了使用 Pipenv 安装和管理依赖),然后 KR 就在 Pipenv 的介绍里宣传这是「官方推荐」,而注明的官方来源则是「Python.org」,这两个关键词背后的超链接都是 PyPA 打包文档的 Pipenv 介绍。packaging.python.org(PyPA) 和 python.org(Python 官方) 的区别很大,很明显,他清楚两者的区别,但又故意没有表达清楚。
退一步讲,不管他的意图是什么,这样的措辞都会让人以为是 Python 官方推荐的打包工具,尤其是对 PyPA 这个组织不了解的人,看到 Python.org 都会认为是 Python 官方。

证据 2

在 PyCon 2018(五月),KR 在演讲《Pipenv: The Future of Python Dependency Management》里介绍 Pipenv 的卖点的时候,列在第一条的仍然是上面那一句「Officially recommended tool from python.org」:

和在 README 里不同的是,这次在演讲上别人没法去点那个 python.org 的链接去甄别究竟是 python.org(Python 官方) 还是 packaging.python.org(PyPA)。而 KR 在介绍这里的时候没有任何说明,直接说是「来自 Python.org 的官方推荐」。

至于 KR 和 PyPA 或者说和 Thea Flowers 有什么关系?把 Pipenv 的介绍加到打包文档是 Thea Flowers 的个人意愿?还是 PyPA 的 35 个成员全部同意的结果?是什么促使 PyPA 在 Pipenv 还不成熟、甚至教程里的内容没法在 Windows 上正常操作的情况下添加到打包教程里?这些问题我暂时找不到答案。

(注:PyPA 指的是 Python Packaging Authority,一个负责维护 Python 打包相关的库(比如 pip、virtualenv 等)和文档的组织。)

从 0 升到 18 的措辞问题

而所谓的18.X.X是 calver versioning(基于日历的版本)

在上一篇文章里,我引用了一段 HN 上的评论来概括 Pipenv 在推广方式上的问题:

Kenneth Retiz 滥用他在 PyPA 的位置(而且快速把一个实际上是 beta 状态的产品的版本号从 0 升到 18)来暗示 Pipenv 已经非常稳定,受到大力支持并且非常官方,但事实却并不是这样。

这句话的英文原文是:

However, Kenneth abused his position with PyPA (and quickly bumped a what is a beta product to version 18) to imply Pipenv was more stable, more supported and more official than it really was.

其中关于版本的部分是「and quickly bumped a what is a beta product to version 18」,可能是我乱翻译造成了误解……我认为原文里的 18 就是一个夸张的表达方式,把这里的数字换成 100 也可以表达同样的意思(也可能是指 0.3.0 跳到 3.0.1 那次)。换用日期版本号(CalVer)那次看起来没什么问题(11.10.4 -> 2018.05.12),所以我认为这里的 18 和日期版本号没关系。

Lockfile 只要过期就重新生成是合理的吗?

Kenneth Reitz 先是说 lockfile 只要是过期了就总是会被重新生成

这是对的,Pipfile和Pipfile.lock是对应的,当执行pipenv install后改了Pipfile,对应的Pipfile.lock就定会改。错误的是,不应该改那些不相关包的版本: 既然已经是==的了,就表明确定了具体版本呀。

这些问题,其实源于 Pipfile对应依赖在一开始没指定具体版本,也就是Pipfile对标requirements.txt,而Pipfile.lock只是当前环境的一个「快照」,如果Pipfile没有明确版本就用Pipfile.lock里面指定的。

我的主要想法是这样的功能实现是不合理的。Pipenv 在安装一个包的时候默认就使用通配符(*)版本写到 Pipfile 是不合理的设计。这样的设计不符合正常的开发流程和使用习惯。如果我在安装一个包的时候就要明确自己要安装哪个版本,以便在 Pipfile 里固定版本,这样会很不方便,而且让 Pipfile.lock 的存在意义变得很弱。

按照 KR 自己的解释,Pipfile 对标的是 requirements.in,Pipfile.lock 对标的是 requirements.txt:

blank

blank
按照大部分人的理解,Pipfile 是所有不固定版本的高层依赖的列表(unpinned),而 Pipfile.lock 是固定安装时采用版本的详细依赖列表(pinned),用来复现程序具体的依赖环境;除非我主动执行 update 命令更新某个依赖,否则 Pipfile.lock 不应该被改动。但实际的 Pipenv 并不是这样,更新 Pipfile.lock 变成了频繁发生(install/uninstall/update)的默认行为。

Poetry 分析依赖慢

Resolving dependencies… (422.9s)

安装个包7分钟,这… 谁能忍?你们试试把bluelog项目的依赖用poetry add加一遍需要多久?我反正体验不下去了

实际测试安装 Flask-SQLAlchemy,解析依赖花了 42 秒(Windows+代理),没有 7 分钟那么夸张。因为解析依赖的结果会被缓存,我就在另一台 Mac(代理)上也试了一遍,结果只花了 8.3 秒。可能是网络状况的问题?

另外我试了把 Bluelog 的所有依赖一次性安装(Windows+代理),其中解析依赖只花了 16.8 秒(因为解析结果缓存的原因,实际也许会稍久一点):

$ poetry add flask flask-ckeditor flask-mail flask-sqlalchemy flask-wtf flask-moment python-dotenv bootstrap-flask flask-login flask-debu gtoolbar gunicorn psycopg2 flask-migrate
Using version ^1.1 for flask
Using version ^0.4.3 for flask-ckeditor
Using version ^0.9.1 for flask-mail
Using version ^2.4 for flask-sqlalchemy
Using version ^0.14.2 for flask-wtf
Using version ^0.9.0 for flask-moment
Using version ^0.10.3 for python-dotenv
Using version ^1.0 for bootstrap-flask
Using version ^0.4.1 for flask-login
Using version ^0.10.1 for flask-debugtoolbar
Using version ^19.9 for gunicorn
Using version ^2.8 for psycopg2
Using version ^2.5 for flask-migrate

Updating dependencies
Resolving dependencies... (16.8s)

所以 Poetry 或许没那么糟糕(当然我还没深入使用过)。

星星

虽然不是决定性的,但是对于这Star不到6K的项目来说我是不敢用的

Pipenv 的 Star 的确很多(而且 README、文档甚至代码里到处都是星星✨),KR 可是个营销专家,但项目质量却并没有那么好。反正我现在一看见星星和蛋糕就有点头疼。

✨🍰✨

另外,顺便说一句,pip(5627)、virtualenv(3189)和 setuptools(883) 的 Star 数量都没到六千……

好吧,我承认最后这两段是在抬杠 :D

不要用 Pipenv

注意:本文写于 2019 年 8 月,其中描述的内容在新版本的 Pipenv 中或已得到修复或改进,请谨慎参考。

如果你是《Flask Web 开发实战》的读者,请访问《Flask Web 开发实战》虚拟环境/依赖/Pipenv 等问题解决方法


Pipenv 让我用的很痛苦,有一种被欺骗的感觉,而且很后悔在《Flask Web 开发实战》里采用它。

大部分情况下,它很好用,但却存在太多问题,有一些问题让人简直没法接受。我知道有人会说「这是开源程序,有 bug 就自己去修」、「爱用不用,没人强迫你」,但问题是,一个进行大肆推广,甚至借 PyPA 做背书来宣传(经常让人误以为是 Python 官方推荐)的工具却连基本的使用流程都没做好,这不是合理和正常的行为。 引用这个 HN 评论的话说就是:

Kenneth Retiz 滥用他在 PyPA 的位置(而且快速把一个实际上是 beta 状态的产品的版本号从 0 升到 18)来暗示 Pipenv 已经非常稳定,受到大力支持并且非常官方,但事实却并不是这样。

在这篇(劝退)文章里,我会分别从包的安装、更新、卸载来测试并指出 Pipenv 的一些问题。

测试准备

  • 项目:Bluelog(一个 Flask 博客)
  • Pipenv 版本:2018.11.26(最新版本)
  • 操作系统:Windows 10,Cmder
  • 测试流程:每一次测试命令前都会删除已经创建的虚拟环境(pipenv –rm),重置 Pipfile 和 Pipfile.lock 文件变动,然后重新创建虚拟环境(pipenv install)。

安装包

假设想给这个旧项目 Bluelog 添加新功能,拿到旧项目的代码,打算安装一个 Flask-Avatars 包。查了文档,发现安装包要使用 pipenv install 命令,所以执行了下面的命令:

$ pipenv install flask-avatars

结果发现其他所有的不相干依赖都被更新了……

WTF,这不是反人类吗(说好的「Python Development Workflow for Humans.」呢)?我安装一个包,默认行为竟然是更新其他所有不相干且已经锁定版本的依赖!

翻了文档才发现,要加一个 –keep-outdated 选项才能避免更新其他锁定的依赖:

$ pipenv install --help
...
--keep-outdated Keep out-dated dependencies from being updated in
Pipfile.lock. [env var: PIPENV_KEEP_OUTDATED]

好吧,那先忍着,多打一个命令行选项就是了:

$ pipenv install --keep-outdated flask-avatars

WTF,为什么所有依赖还是被更新了?

好吧,有 bug 很正常,我来提个 issue 吧,哎,好像有很多 issue 了?

重点评论:

Kenneth Reitz 先是说 lockfile 只要是过期了就总是会被重新生成(这是什么逻辑?),接着又说用 pipenv update depname,但其他人都回复不起作用(我下面会进行单独测试)。

接着,看到其他评论提到用 –selective-upgrade 选项:

$ pipenv install --help
...
--selective-upgrade Update specified packages.

我又继续使用 –selective-upgrade 选项:

$ pipenv install --selective-upgrade flask-avatars

仍然会更新所有依赖……

对了,顺便还测试了这个命令,依然没用:

$ pipenv install --keep-outdated --selective-upgrade flask-avatars

除了安装某个包会导致所有依赖版本被更新,Pipenv 在解决依赖的冲突上面也有一些不足,比如执行下面的安装命令(具体见 Poetry README):

$ pipenv install oslo.utils==1.4.0

会提示无法安装成功:

ERROR: ERROR: Could not find a version that matches pbr!=0.7,!=2.1.0,<1.0,>=0.6,>=2.0.0

更新包

假设我想更新 Bluelog 这个项目用的 Flask 版本(从 1.0.2 更新到最新的 1.1.1)。查了文档,找到了 update 命令(https://docs.pipenv.org/en/latest/basics/#example-pipenv-upgrade-workflow),于是我执行下面的命令:

$ pipenv update flask
Locking [dev-packages] dependencies…
Success!
Locking [packages] dependencies…
Success!
Updated Pipfile.lock (fd55e3)!
Installing dependencies from Pipfile.lock (fd55e3)…
================================ 26/26 - 00:00:12
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
All dependencies are now up-to-date!

突然看到最后一行赫然写着「All dependencies are now up-to-date!」,我以为是搞错了,赶紧看了下 Pipfile.lock,WTF,为什么我所有的依赖(包括和 Flask 完全不相关的)又都被更新了?
依然,已经有很多相关 issue:

重点评论:

如果这个 issue 没有被锁定,这一句「I have no idea.」下面的图标不知道还会被点多少次。我猜 Kenneth Reitz 对这个 issue 让多少人头疼也没有 idea。

继续搜索,查文档,发现 update 命令也有 –keep-outdated 和 –selective-upgrade 两个选项:

$ pipenv update --help 
... 
--selective-upgrade Update specified packages.
--keep-outdated Keep out-dated dependencies from being updated in
Pipfile.lock. [env var: PIPENV_KEEP_OUTDATED]

先来试下 –keep-outdated:

$ pipenv update --keep-outdated flask

no luck,还是更新了所有依赖。继续试一下 –selective-upgrade:

$ pipenv update --selective-upgrade flask

依然没用,仍然会更新所有依赖……

继续查 issue,发现下面这些:

在 #3461 里发现了下面这个评论:

(因为 Frost Ming 是国内的同学,也是核心维护者,说明一下,这里无意冒犯,引用这个评论只是想说明 Pipenv 现在的开发状态。)

这段评论的重点是「In fact, the package name passed as argument is not used at all.」。

也就是说,pipenv update 实际上是不接受包名称参数的。这在下面这个评论也得到了印证:

Here is the important caveat:pipenv updatealwaystargets every package in your lockfile, without exception. It does not accept arguments.

一个还没实现的功能就写到文档里了?这真的不是开玩笑吗?不仅是写到了文档里,还写到了命令行帮助文档里:

$ pipenv update --help
Usage: pipenv update [OPTIONS] [PACKAGES]...

类似下面的场景:

  • 用户:怎么运行程序呢?好,查下文档,文档里说「执行 run 命令就可以运行程序」。哎?怎么没用?
  • 开发者:哦,这个功能还没实现,先写出来让你练练手。

最终的结果就是,如果你想更新一个包,那就只能手动把更新版本的包版本和 hash 编辑到 Pipfile.lock 里。这么做实在是太蠢了。

卸载包

假设我决定不再使用 Gunicorn,需要卸载它,在文档里查到 pipenv uninstall 命令https://docs.pipenv.org/en/latest/basics/#pipenv-uninstall),于是执行下面的命令:

$ pipenv uninstall gunicorn

结果呢?为什么我所有的依赖又都被更新了!?好,我已经习惯了。看命令行帮助文档,同样有 –keep-outdated 命令:

$ pipenv uninstall --help
...
--keep-outdated Keep out-dated dependencies from being updated in
Pipfile.lock. [env var: PIPENV_KEEP_OUTDATED] 

再试一下,虽然我已经不抱期待了:

$ pipenv uninstall --keep-outdated gunicorn

顺便说一句,卸载的另一个问题是,当你卸载一个包的时候,只会卸载这个包本身,而这个包引入的相关依赖都会被保留,需要手动使用 clean 或 sync 命令修正(参考 Poetry README)。

不要使用 Pipenv(至少是现在)

当然,Pipenv 一直在改进。比如 Windows 支持,Lock 很慢的问题,都有过很多的优化(暂且不提没优化之前的痛苦经历)。

但是,种种证据都在表明,这其实是一个半成品。承诺了很多,兑现的却很少。或许过一段时间等它真正成熟了,能够保证基本使用流程,并且可以修改哪些反人类的设定以后再考虑用它(我怀疑这一条是否能实现,除非完全「去 Kenneth Reitz 化」,并且有一个核心维护者能够来推动执行)。

现在,请不要使用它。

我很抱歉在《Flask Web 开发实战》以及文章《Pipenv:新一代Python项目环境与依赖管理工具》中,推动更多人用它,给大家带来潜在的麻烦。我计划了一些补救措施,会逐一执行:

  • 写这篇文章【DONE】
  • 给《Flask Web 开发实战》的五个实例项目追加 requirements.txt 文件,并在 README 中添加说明。
  • 如果《Flask Web 开发实战》能出第二版,修改所有 Python 包安装命令,去掉所有 Pipenv 相关介绍。
  • 写文章介绍替代的几种工具和用法,包括原有的 virtualenv+pip、virtualenvwrapper、Poetry 等。
  • 在 PyCon China 2019 的闪电演讲《Python 虚拟环境和依赖管理工具大乱斗》里提及这个信息。
  • 以后写推荐文章时对这种带着强烈个人风格的项目保持警惕,并对 Kenneth Reitz 这个名字相关的东西保持警惕。

你可以选择用回 virtualenv+pip(+virtualenvwrapper),或是尝试新工具,我会在下一篇文章介绍主要替代品 Poetry 的基本用法。

相关文章

(1)

PyCon China 2019 Tutorial:Python Web 开发第一课

这是在 PyCon China 2019 上海场 9 月 22 号上午九点开始的 Tutorial(实践课程)《Python Web 开发第一课》 的介绍和相关信息。

购票链接:https://www.bagevent.com/event/5886131(Tutorial T3,优惠码 hellogrey)

标题

Python Web 开发第一课

介绍

这是一个面向 Python 程序员的 Web 开发课程,目标听众需要对 Python 基本语法有一定的了解,但对 Web 开发的了解程度没有要求。

在这个课程里,我会将 Python Web 开发所涉及的相关概念进行一个系统的梳理和介绍,包括 HTTP 协议、前端基础知识、常用的 Python Web 框架以及其他各种工具。

这个课程还会包含一个动手编程的环节。我会从最让人头疼的开发环境搭建开始,一步一步教你如何使用 Flask 开发一个简单的 Web 程序。

在课程过后,参与者会对整个 Python Web 开发技术栈有一个全局认识,并掌握基本的 Web 开发知识,而且会对接下来的学习路径有一个清晰的了解。

流程

时长:三小时

一、基本概念

  • Python Web 开发技术栈地图
  • HTTP 协议基础知识(请求与响应、URL 等)
  • 前端基础知识(HTML、CSS、JavaScript、AJAX 等)
  • Python 后端框架的特点和选择(Flask、Django 等)
  • 传统 Web 程序和 Web API 的对比
  • 测试、部署、持续集成等相关概念快速扫盲

二、动手编程

  • 开发环境搭建
  • 运行和调试程序
  • 编写 HTML 模板
  • 添加表单支持
  • 添加数据库支持

三、Q&A

  • 介绍常见的学习误区和建议的学习方向
  • 关于代码或其他任何相关内容的提问

内容难度

初级

目标听众

  • 想了解 Web 开发的前端、运维、测试或其他工程师
  • 想自己做网站的编程爱好者
  • Web 开发或 Python 初学者

听众要求

  • 了解 Python 基本语法
  • 有一台安装了 Python 和浏览器的电脑,并且了解命令行基本操作

讲者介绍

李辉,Flask 等相关项目的维护者,《Flask 入门教程》和《Flask Web 开发实战》的作者,HelloFlask 社区创建者。他撰写过大量技术文章,回答过大量技术问题,在这个过程中积累了一些编程教学的技巧,擅长用简单的语言解释复杂的编程概念。你可以在他的个人网站 greyli.com 了解到更多相关信息。

PyCon China 2019:基于 Flask 的 Web API 开发指南(北)

Meta

成都场 2.0 版本(推荐)

上海场 1.0 版本


这是在 PyCon China 2019 上海场 9 月 21 号分会场 B 下午 1:30 开始的演讲《基于 Flask 的 REST API 开发指南》 的介绍和相关信息。

这场演讲也会参加 PyCon China 2019 成都场(10 月 26 号)。

标题

基于 Flask 的 Web API 开发指南

介绍

作为一个微框架,轻量灵活的 Flask 很适合用来开发 Web API。相对于 Django REST Framework 和 APIStar,Flask 有什么优势和缺点?为了减少工作量,我们通常会使用一些工具来辅助编写,面对 Flask-RESTful、Flask-RESTPlus、Flask-API、Webargs、Marshmallow 等扩展和工具库,我们应该如何选择?虽然我们经常使用 REST API 这个名称,但是大部分的 API 都不够 RESTful,那么什么样的 API 才能算是 REST API?在这个议题中,我们将对这几个问题逐一进行探讨,并了解如何使用 Flask 编写出功能完善的 Web API。

总结

尽管完成了两版,但是比预先计划的内容少了很多,没能完成 Flask 扩展和其他 Web API 框架的深入对比。这些估计要放到新书里了。

  • 上海站第一版花费时间:23h 31m
  • 成都站第二版花费时间:31h 51m

PyCon China 2019:Python 虚拟环境和依赖管理工具大乱斗

这是在 PyCon China 2019 上海场 9 月 21 号分会场 B 下午 4:40 开始的闪电演讲《Python 虚拟环境和依赖管理工具大乱斗》(时长限定 5 分钟以内) 的介绍和相关信息。

PyCon China 2019 上海场购票链接:https://www.bagevent.com/event/5293611(优惠码 helloflask)

Meta

标题

Python 虚拟环境和依赖管理工具大乱斗

介绍

大多数人都会在 Python 虚拟环境和依赖管理的路上经历重重困难。一开始,你使用 pip + virtualenv + requirements.txt 的工具组合,或者再加上 virtualenvwrapper;接着,更高级的新东西出现了。Pipenv 号称新一代 Python 项目环境和依赖管理工具,打算替代上面的复杂组合,但是发展并不顺利;接着,竞争者出现了,其中实力最强的 Poetry 除了可以替代 Pipenv 来管理依赖之外,甚至还可以让你不用写 setup.py。故事就这样结束了吗?当然没有,尚在草案阶段的 PEP 582 在一边虎视眈眈,试图终结这一切混乱……

总结

加上写相关研究文章的时间,一共花了 36 小时来准备这个演讲(5 分钟),整体效果还不错,后半部分时间太紧说的有点快。

另外最后几天太忙(懒),有几处幻灯片页面样式都使用了行内 CSS,暂时没时间改,就这样吧 :/