Pipenv:Python 官方钦定的包管理工具
今天给大家介绍的是 python 包管理工具 Pipenv。在 Python 的官方文档中,有这么一段话:
Use Pipenv to manage library dependencies when developing Python applications … Consider other tools such as
pip
whenpipenv
does not meet your use case.”
也就是说 Python 官方认为 Pipenv 比 pip 更加适合一般的用户。作为一个第三方工具,能从官方获得如此高的评价实属不易。这篇文章就向大家简要介绍一下这个工具。
↑ xkcd: Python Environment
背景知识
在进入主题之前,首先我们要区分 Pythpn 社区中几个名字非常类似的工具。这里推荐 stackoverflow 上 Flimm 的回答,讲的十分清楚了。摘抄翻译一下本文会讲到的几个工具:
- PyPI 中的非标准库
virtualenv
是一个非常流行的、用来创建虚拟环境的工具。如果你不熟悉这个工具,我强烈推荐你去学习一下,因为它真的非常有用。接下来的回答中我都会拿他进行对比。 它的原理是在一个目录下(比如:env/
)安装很多文件,然后在PATH
环境变量的前面加上一个自定义的bin
目录(比如:env/bin
)。一个完整的python
或者python3
二进制文件会放入这个目录中,Python 被设计成先在相对它的路径处寻找库。它不是 Python 标准库的一部分,但是它隶属于 PyPA (Python Packaging Authority) 。激活了之后,你可以直接通过 pip 在虚拟环境中安装库。pyenv
用来隔离 Python 版本。比如你可能想要在 Python 2.6、2.7、3.3、3.4 和 3.5 中测试你的代码,所以你需要一种在它们之间进行切换的方式。激活之后,它会在 PATH 前面加上~/.pyenv/shims
,这里储存了一些 Python 命令程序 (python
,pip
) 。这些文件不是从系统已有的 Python 中复制出来的,而是专门用来根据环境变量PYENV_VERSION
、.python-version
文件或者~/.pyenv/version
文件来决定使用哪个 Python 版本。pyenv 也简化了下载和安装多个 Python 版本的步骤,使用pyenv install
命令即可。pipenv
, 由 Kenneth Reitz 编写 (requests
的作者)(国内也称作 K 神——译者注),是这个列表当中最新的一个项目。它将Pipfile
,pip
和virtualenv
结合到同一个命令行命令。virtualenv
目录一般放在~/.local/share/virtualenvs/XXX
, 其中的XXX
是项目目录的 hash。这和 virtualenv 不一样,后者一般放到当前的工作目录。- 标准库
venv
是 Python 3 内置的一个包(从 Python 3.3 版开始——译者注)。你可以通过python3 -m venv
来运行它。它的目标和实现方式都和virtualenv
非常类似,只不过它不需要复制 Python 二进制文件(Windows 除外)。除非你要使用 Python2,不然你应该选择venv
。不过截止本文时,Python 社区更喜欢virtualenv
,venv
并没有什么声音。
↑ venv 的心情如图
安装
官方文章的 Installing Pipenv 一章有好几种安装的方式。在不是很重要的机器上(比如自己的开发机)可以直接使用 pip install pipenv
进行安装。但是如果在生产环境上安装,就需要其他方式。这里我介绍一下 User Installs 的方式。这种方式虽然有点繁琐,但是胜在安全,不会影响已有的 Python 环境。
[superman@server ~ ]$ pip3 install --user pipenv
100% |████████████████████████████████| 5.0MB 314kB/s
Successfully installed pipenv-2018.7.1
[superman@server ~ ]$ which pipenv
pipenv not found
注意此时由于没有修改 PATH
(环境变量),所以还需要找到用户目录并修改并将下面的 bin
目录加入 PATH
中。
在 Linux 和 macOS 下,用户目录可以使用 python -m site --user-base
命令获得:
[superman@server ~ ]$ python3 -m site --user-base
/home/superman/.local
[superman@server ~ ]$ export PATH=$PATH:$(python3 -m site --user-base)/bin
[superman@server ~ ]$ which pipenv
/home/superman/.local/bin/pipenv
为了方便起见,大家可以修改 ~/.profile
或 ~/.bashrc
或者类型的文件,加入上面设置 PATH
的语言,让 shell 在每次登录的时候自动设置 PATH
。
同时为了避免不同的工具链之间的相互影响,通过 User Install 方式安装的「工具链」(比如 virtualenv、pyenv 和 tox)要尽可能少。
基本用法
使用 pipenv install <packagename>
安装包。
$ cd myproj
$ pipenv install requests
Using /usr/bin/python3 (3.5.2) to create virtualenv...
Virtualenv location: /root/.local/share/virtualenvs/myproj-y53uuyLx
Creating a Pipfile for this project..
Adding requests to Pipfile's [packages]...
Pipfile.lock not found, creating...
Updated Pipfile.lock (0b4483)!
Installing dependencies from Pipfile.lock (0b4483)...
在第一次执行 pipenv install
的时候,Pipenv 会做这么几件事情:
- 创建 virtualenv 环境
- 创建
Pipfile
文件 - 创建
Pipfile.lock
文件 - 安装依赖
其他常用命令:
pipenv install
:安装Pipfile
中[packages]
下面的包pipenv install --dev
:安装Pipfile
中[packages]
和[dev-packages]
下面的包pipenv install <package>
:在当前环境安装包,并将相关信息写入Pipfile
的[packages]
和Pipfile.lock
pipenv install <package> --dev
:在当前环境安装包,并将相关信息写入Pipfile
的[dev-packages]
和Pipfile.lock
pipenv uninstall <package>
: 在当前环境卸载包,并将相关信息从Pipfile
和Pipfile.lock
中移除pipenv lock
:确认Pipfile
中所有包已安装,并根据安装版本生成Pipfile.lock
pipenv --rm
:删除 virtualenv 环境pipenv --venv
:输出当前 virtualenv 环境的路径pipenv shell
:进入虚拟环境,类似 venv 和 virtualenv 的source ./venv/bin/activate
pipenv run <command>
: 使用虚拟环境跑一些命令,比如pipenv run pip3 list
Pipenv 解决了哪些问题?
虚拟环境和 pip 不能同时使用
venv 和 virtualenv 可以提供独立的 Python 环境,这是一个非常必要的功能。但是每次进入和退出环境都需要手动地执行命令,非常繁琐而且容易出错。
举个例子,projectA
和 projectB
各有一个 venv 环境(位置是两个项目下的 .venv
目录),我想在两个 venv 环境中安装不同版本的库,正确的做法是下面这样的:
$ cd projectA
$ . .venv/bin/active
(venv) $ pip3 install requests==2.0 # 安装在 projectA/.venv/
(venv) $ cd ../projectB
(venv) $ which pip3
/tmp/projectA/venv/bin/pip3
(venv) $ deactive
$ . .venv/bin/active
(venv) $ pip3 install requests==2.1 # 安装在 projectB/.venv/
可以看到,切换目录并不会自动地切换 venv 环境。这样不仅繁琐,而且容易造成失误,因为我可能没有注意到我所在的目录和使用的 venv/virtualenv 环境不同。
使用 Pipenv 的话,就能解决这个问题了,这也是诸如 npm 等其他包管理器的行为。
$ cd projectA
$ pipenv install requests==2.0 # 安装在 ~/.local/share/virtualenvs/projectA-OOSnJKux/
$ cd ../projectB
$ pipenv install requests==2.1 # 安装在 ~/.local/share/virtualenvs/projectB-XaGlAV6H/
$ cd projectA
$ npm install express-generator@4.0 # 安装在 projectA/node_modules/
$ cd ../projectB
$ npm install express-generator@4.2 # 安装在 projectB/node_modules/
pip 不能精确地指定版本
今天在开发 Python 项目的时候,一般会使用一个 requirements.txt
文件来保存依赖信息,这也是标准的做法。而 requirements.txt
有两种使用方法:
第一种方式,只保存顶级依赖:
$ cat requirements.txt
requests[security]
flask
gunicorn==19.4.5
因为依赖及其子依赖版本并没有被固定下来,在执行 pip install -r requirements.txt
时会有不同的结果,可能会造成一些问题。
第二种方式,
$ cat requirements.txt
cffi==1.5.2
cryptography==1.2.2
enum34==1.1.2
Flask==0.10.1
gunicorn==19.4.5
idna==2.0
ipaddress==1.0.16
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
ndg-httpsclient==0.4.0
pyasn1==0.1.9
pycparser==2.14
pyOpenSSL==0.15.1
requests==2.9.1
six==1.10.0
Werkzeug==0.11.4
这种方式所有的依赖,包括项目的依赖、和依赖的依赖,而且每个依赖都精确到具体的版本,这也是当前 Python 社区的最佳实践。顺便一提这些信息可以使用 pip freeze
命令输出。但是使用这种方式,管理起来不方便,没有办法很好的分清楚哪些依赖是项目直接需要的,哪些依赖是子依赖。尤其是在升级依赖版本的时候,这种混乱会造成很多困难。
Pipenv 的解决方案是 Pipfile
和 Pipfile.lock
,使用它们来替代老旧过时的 requirements.txt
Pipfile
Pipfile
使用了 TOML 格式而不是 requirements.txt
的纯文本,其中储存了生产环境([packages]
)和开发环境([dev-packages]
)下所需要的顶级依赖。下面有个简单的例子
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
requests = "*"
[dev-packages]
pytest = "*"
Pipfile.lock
Pipfile.lock
实际上是一个 JSON 文件,保存着所有的依赖的版本和 hash 信息。这个文件应该提交到 git 仓库里(除非你同时面向多个 Python 版本开发),而且在任何情况下,都不应该手动地修改这个文件。
一个 Pipfile.lock
文件的例子:
{
"_meta": {
"hash": {
"sha256": "8d14434df45e0ef884d6c3f6e8048ba72335637a8631cc44792f52fd20b6f97a"
},
"host-environment-markers": {
"implementation_name": "cpython",
"implementation_version": "3.6.1",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "16.7.0",
"platform_system": "Darwin",
"platform_version": "Darwin Kernel Version 16.7.0: Thu Jun 15 17:36:27 PDT 2017; root:xnu-3789.70.16~2/RELEASE_X86_64",
"python_full_version": "3.6.1",
"python_version": "3.6",
"sys_platform": "darwin"
},
"pipfile-spec": 5,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704",
"sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5"
],
"version": "==2017.7.27.1"
},
"chardet": {
"hashes": [
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
],
"version": "==3.0.4"
},
"idna": {
"hashes": [
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
],
"version": "==2.6"
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
],
"version": "==2.18.4"
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
],
"version": "==1.22"
}
},
"develop": {
"py": {
"hashes": [
"sha256:2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a",
"sha256:0f2d585d22050e90c7d293b6451c83db097df77871974d90efd5a30dc12fcde3"
],
"version": "==1.4.34"
},
"pytest": {
"hashes": [
"sha256:b84f554f8ddc23add65c411bf112b2d88e2489fd45f753b1cae5936358bdf314",
"sha256:f46e49e0340a532764991c498244a60e3a37d7424a532b3ff1a6a7653f1a403a"
],
"version": "==3.2.2"
}
}
}
Pipfile.lock
默认使用 sha256 算法给每一个包进行 hash,这样可以保证在不安全的网络环境下也能下载到正确的包。
Pipfile
和 Pipfile.lock
都应该提交到版本库中。
Pipenv 的其他特性
为什么 Pipenv 使用 virtualenv 而不是标准库中的 venv?
根据这个 issue 中的讨论,Pipenv 不使用 venv 的原因有两个:一是 Kenneth Reitz 个人更喜欢 virtualenv,二是 Pipenv 用来管理 virtualenv 的库 Pew 目前还不支持 venv。
将 virtualenv 环境的位置改为工程目录下
Pipenv 默认会将 virtualenv 目录创建在 ~/.local/share/virtualenvs/
中,但是很多人希望可以将 virtualenv 目录放在工程目录下。Pipenv 也提供了相应的功能,只要将环境变量中 PIPENV_VENV_IN_PROJECT
的值设为 true
,Pipenv 就会在工程目录下的 .venv
目录创建 virtualenv 环境。不过 Pipenv 也的确提供了足够的命令用来操作 virtualenv,所以不把 virtualenv 目录放在工程目录下也是可以接受的。
$ export PIPENV_VENV_IN_PROJECT=true
$ cd projectC
$ pipenv install
Creating a virtualenv for this project...
Virtualenv location: /root/projectD/.venv
$ pipenv --venv
/root/projectC/.venv
2019-02-13 更新:另外一个简单的办法是在项目根目录下手动创建 .venv
目录,然后再执行 pipenv install
。Pipenv 会自动在工程目录下创建虚拟环境。
使用不同版本的 python 程序
Pipfile 中有一个可选的 python_version 字段:
[requires]
python_version = "3.7"
Pipenv 会自动地根据这个字段寻找相应的 Python 版本。如果找不到对应版本,而且安装了 pyenv 的情况下,Pipenv 还会询问你是否要使用 pyenv 安装特定版本的 Python,可以说是非常贴心了。
$ pipenv install
Warning: Python 3.7 was not found on your system...
Would you like us to install CPython 3.7.0 with pyenv? [Y/n]: y
Installing CPython 3.7.0 with pyenv (this may take a few minutes)...
有空写一篇博客介绍一下 pyenv
自定义脚本快捷命令
Pipenv 支持在 Pipefile
中的 [scripts]
表情内添加自定义的脚本命令,并通过 pipenv run <shortcut name>
的方式在 virtualenv 环境中执行对应的命令,哪怕在之前并没有手动地激活 virtualenv 环境。
比如在 Pipefile
中添加下面的代码:
[scripts]
test = "python3 -m unittest discover -s ./tests"
dev = "python3 manage.py runserver 0.0.0.0:8000"
然后执行 pipenv run test
就相当于执行了 pipenv run python3 -m unittest discover -s ./tests
,pipenv run dev
相当于 pipenv run python3 manage.py runserver 0.0.0.0:8000
。非常方便的功能。
继续学习 Pipenv
这篇文章只是一个简要的概述,如果大家希望能够深入了解 Pipenv,可以去看官网的 Further Documentation Guides 章节,应该可以覆盖绝大多数的使用场景了。
总结
虽然 virtualenv 不是 Pipenv 发明的,Pipfile 也不是它第一个使用的。但是 Pipenv 能把这些工具集合在一起,再加上极其人性化的设计,是解决 Python 包管理问题的不二之选。Pipenv 完全能配得上 Python 官方给他的褒奖和推荐,推荐大家亲自去使用和感受。