Python 包管理编年史

Table of content:


About

看到 pip,distutils、distutils2, setuptools、easy_install、ez_setup.py, pip, setup.py 是不是会崩溃! 而这里的时间先后顺序是:

  • distutils 是 python 标准库的一部分,2000年发布。使用它能够进行 python 模块的安装和发布。
  • distutils2 被设计为 distutils 的替代品,后来这个计划停滞了。
  • setuptools 是一个为了增强 distutils 而开发的集合,2004年发布。它包含了 easy_install 这个工具。
  • ez_setup.py 是 setuptools 的安装工具。ez 是 easy 的缩写,比如一些简答的用法:
  • distribute 是 setuptools 的一个分支版本,后来又合并到了 setuptools。
  • pip 是目前 python 包管理的事实标准,2008年发布。它被用作 easy_install 的替代品,但是它仍有大量的功能建立在 setuptools 组件之上。
  • pip 希望不再使用 Eggs 格式(虽然它支持 Eggs),而更希望采用「源码发行版」(使用 python setup.py sdist 创建)

关于 zip, eggs, wheel 的格式

  • 一开始 distutils 是 python steup.py sdit, 打包成 zip 或 tar.gz 格式
  • 然后 setuptools 出现了 eggs 格式
  • 再后来 wheel 格式想替代 eggs 格式

wheel 本质上是一个 zip 包格式,它使用 .whl 扩展名,用于 python 模块的安装,它的出现是为了替代 Eggs。
wheel 还提供了一个 bdist_wheel 作为 setuptools 的扩展命令,这个命令可以用来生成 wheel 包。
wheel 包是一种预编译的包,不需要系统里安装对应的头文件就可以使用,可以极大程度简化构建的步骤。

Python Wheels上提到的优势还有:

  1. Faster installation for pure Python and native C extension packages.
  2. Avoids arbitrary code execution for installation. (Avoids setup.py)
  3. Installation of a C extension does not require a compiler on Windows or macOS.
  4. Allows better caching for testing and continuous integration.
  5. Creates .pyc files as part of installation to ensure they match the Python interpreter used.
  6. More consistent installs across platforms and machines.

Python Wheels 上能看到使用 Wheels 发行的 python 模块在 PyPI 上的占有率,同时各个包管理工具也进行了支持:

  • pip 支持 wheel,(需要安装 wheel 模块), 不再支持 eggs (原来 eggs 用 esay_install 安装)
  • setup.cfg 可以用来定义 wheel 打包时候的相关信息。
  • buildout 在 2.8.0 以后支持了 wheel 包的安装,wheel 包是一种预编译的包,不需要我们系统里安装对应的头文件就可以使用,可以极大程度简化构建的步骤。
    在 buildout.cfg 中增加一行 extensions = buildout.wheel :
1
2
3
4
5
6
[buildout]
index = https://mirror.xxx.com/simple/
update-versions-file = versions.cfg
extends = versions.cfg
develop = .
extensions = buildout.wheel

在依赖管理上,目前有以下几种方案

pip + requirement.txt

  • pip 可以利用 requirments.txt 来实现依赖的安装。
  • 支持 git/svn/hg 等流行的 VCS 系统,可以直接从 gz 或者 zip 压缩包安装,支持搜索包,以及指定服务器安装等等功能。

很多开源项目都是把依赖写在 requirement.txt 里面。然后 pip install -r requirements.txt

但是这样有几个问题:

buildout

buildout 的使用可以看下这个文档: http://www.buildout.org/en/latest/getting-started.html 这里只提一些最佳实践和发现过的问题。

版本锁定:

在项目根目录新建一个 versions.cfg 的文件, 锁定依赖的版本号

1
echo "[versions]" > versions.cfg

在 buildout.cfg 指定 extends,一个 demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[buildout]
index = https://your_mirror.xxx.com/simple/
relative-paths = true # 使 buildout 生成的 bin 文件和当前路径无关,方便文件夹移动位置后复用
update-versions-file = versions.cfg # ask Buildout to maintain our versions.cfg file for us
extends = versions.cfg # 指定一个额外的 versions.cfg 的文件
newest = false # 如果为 true 则永远选用最新的稳定版
develop = .
parts = app
extensions = buildout.wheel # buildout 在 2.10.0 以后不需要配置 extensions 就可以安装 wheel 包了,原因是 setuptools 原生支持了 wheel 扩展。确保 setuptools >= 38.2.3
eggs = cython
datasync-api

[app]
recipe = zc.recipe.egg
interpreter = python
eggs = ${buildout:eggs}
pytest
mock

在项目中执行 buildout ,buildout 会自动将当前使用的依赖版本 dump 到 versions.cfg 中。

buildout 的一些坑

但是其实 buildout 没有做到和系统的隔离,比如以下是遇到过的坑:

  • 如果系统里面有一个包了,buildout 就不会下载 eggs 到对应的目录下
  • 如果 eggs 里面有一个包,但是系统也有,会优先用系统的

pipenv, Python 官方推荐的管理依赖工具

pipenv 想要解决什么问题 ?
  • 你不用 pip 和 virtualenv 需要两者分开使用了
  • 使用 requiresments.txt 的方式来管理依赖有很多问题,上面已经描述过了。而 pipenv 用 Pipfile 和 Pipfile.lock 来解决了需要的依赖,以及依赖的依赖版本的问题。
  • 可以用 pipenv graph 看到依赖树

基本使用:

1
2
pipenv install requests
# 这个步骤会生成一个 Pipfile 和 Pipfile.lock, 依赖会被安装在一个默认的位置
1
2
# 如果想要更改这个默认的配置,比如,和 project 路径下,可以参考
PIPENV_VENV_IN_PROJECT=. pipenv --two install requests

Pipfile 里面会有一些依赖信息

1
2
3
4
5
6
7
8
9
10
11
12
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]
requests = "*"

[dev-packages]

[requires]
python_version = "3.6"

Pipenv.lock 文件,里面有所有包依赖的版本和 hash 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"_meta": {
"hash": {
"sha256": "33a0ec7c8e3bae6f62dd618f847de92ece20e2bd4efb496927e2524b9c7b8df8"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
],
"index": "pypi",
"version": "==2.18.4"
}
},
"develop": {}
}

查看锁版本的信息

1
pipenv lock -r  
1
2
3
4
5
certifi==2018.1.18
chardet==3.0.4
idna==2.6
requests==2.18.4
urllib3==1.22

查看依赖的图

1
pipenv graph

可以看到一个依赖,以及它依赖的版本的结构

1
2
3
4
5
requests==2.18.4
- certifi [required: >=2017.4.17, installed: 2018.1.18]
- chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
- idna [required: <2.7,>=2.5, installed: 2.6]
- urllib3 [required: >=1.21.1,<1.23, installed: 1.22]
1
2
pipenv lock -r
# 从 Pipfile 生成 Pipfile.lock, 但是实验发现如果依赖版本写在 setup.py 里面是不会锁版本到 Pipfile.lock 里面的
1
2
pipenv install -e .
# 将 setup.py 里面的依赖锁到 Pipfile.lock 中

virtual env 管理

tox

tox 是一个虚拟环境管理命令行工具,

  • checking your package installs correctly with different Python versions and interpreters * 检查你的软件包能否在不同的 Python 版本或解释器下正常安装
  • running your tests in each of the environments, configuring your test tool of choice 在不同的环境中运行运行你的测试代码
  • acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. (没懂具体是什么样子)

安装

1
2
pip install tox
tox

tox 会去读 setup.py 同目录下的 tox.ini 配置文件,可以通过 tox-quickstart 来生成配置文件,以下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[tox]
# 指定源
indexserver =
default = https://mirror.xxx.com/simple

# 支持的版本
envlist = py27, py3

[testenv]
commands =
{envbindir}/python setup.py develop # ???
pytest --cov-report term-missing --cov-report xml --cov=diplomat tests/
# 测试的依赖
deps =
pytest
pytest-cov
flexmock

Reference:

venv

Python3

1
2
3
4
5
6
7
8
9
10
11
12
# start, 试用于 Python3
ᐅ python3 -m venv .venv # 这时会在当前路径出现 .venv 的目录,或者也可以 virtualenv .
ᐅ ls .venv
bin include lib pyvenv.cfg

# acticate,激活环境
ᐅ source .venv/bin/activate

# 这样 Python path 就是: {your project path}/.venv/bin/python 了

# leave
ᐅ deactivate

Python2

1
2
3
4
virtualenv -p /usr/bin/python2.7 .venv  #在当前路径 的 .venv 下创建虚拟环境
source bin/activate # 激活环境

# 这样 Python path 就是: {your project path}/bin/python 了

virtualenvwrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# install
pip install virtualenvwrapper

# save to ~/.zshrc or ~/.bashrc
export WORKON_HOME=$HOME/.virtualenvs
export PROJECT_HOME=$HOME/Devel
source /usr/local/bin/virtualenvwrapper.sh

# activate a env
mkvirtualenv name
workon name

# delete a env
deactivate name
rmvirtualenv mynewenv

后续 todo

  • 更加完善每个工具背后的好处坏处

Reference

  • https://docs.pipenv.org/
  • Pipfile 和 setup.py:
    • Library: Libraries provide reusable functionality to other libraries and applications (let’s use the umbrella term projects here). They are required to work alongside other libraries, all with their own set of subdependencies. They define abstract dependencies. To avoid version conflicts in subdependencies of different libraries within a project, libraries should never ever pin dependency versions. Although they may specify lower or (less frequently) upper bounds, if they rely on some specific feature/fix/bug. Library dependencies are specified via install_requires in setup.py.
    • Library 还是需要在 setup.py 里面指定依赖的版本,可以通过 pipenv install -e . 来将 setup.py 里面的依赖锁到 Pipfile.lock 中, 直接 pipenv install 是不会进行这个步骤的
  • 一些高阶用法 https://docs.pipenv.org/advanced/#pipfile-vs-setuppy
  • wheel https://www.python.org/dev/peps/pep-0427/
  • Python Wheels
  • 包管理工具的一些梳理
  • https://realpython.com/pipenv-guide/
  • wen 对 pip 的一些调研
    1. 使用这些参数实例化 PipSession, PackageFinder 这两个类。 其中 PipSession 是对 requests.session 的包装,就做一些网络操作, 下载 wheel 包或者 sdist 包都需要用到它。 PackageFinder 的作用就和它名字一样,它会根据你设置的参数:什么平台、 什么版本去 index server 上找合适的包。
    2. 用实例化出来的 session 和 finder 去构造 RequirementSet。这个类 的构造函数允许传 18 个参数,就问你怕不怕。pip 会用它去帮我们下载、安装包 以及包的依赖。RequirementSet 这个类干的事情太多了,基本上干 每件事情都需要用到它。很奇葩。
  • virtualenvs http://docs.python-guide.org/en/latest/dev/virtualenvs/
  • 这里有一些系统的分析:http://docs.python-guide.org/en/latest/dev/virtualenvs/

关于头图

  • 拍摄自迪拜 Mall
Python schedule 源码阅读
Hexo Introduction