英文 | The state of Python Packaging【1】python
原做 | BERNAT GABORgit
译者 | 豌豆花下猫github
声明 :本文得到原做者受权翻译,转载请保留原文出处,请勿用于商业或非法用途。ide
pip
19.0 已经于 2019 年 1 月 22 日发布。在其功能列表中,最值得注意的是它如今支持 PEP-517,默认状况下是支持的,若是项目的根目录中有一个 pyproject.toml。该 PEP 于 2015 年建立,并于 2017 年被接受。尽管 pip 花了一段时间才实现它,但该版本及其后续问题却代表,不少人根本不熟悉它。函数
若是你想了解 Python 打包(packaging)生态的现状及未来如何演变,请继续阅读。咱们但愿,即便上述提到的 Python 加强提案(译注:即 PEP,关于 PEP 的介绍,请阅读这篇文章),现在可能会引发一些不愉快,但从长远来看,咱们将从中受益。工具
我大约在三年前加入了 Python 开源社区(尽管使用它已有 8 年之久)。从早期开始,我就据说 Python 打包有一点黑匣子的名声。它有不少未知的内容,人们一般只复制其它项目的构建配置文件,就使用上了。性能
在尝试更好地理解这个黑匣子,并对其进行改进的过程当中,我已经成为了 virtualenv 和 tox 项目的维护者,偶尔也为 setuptools 和 pip 作些贡献。单元测试
我但愿对这个主题进行详尽的(并但愿是一个较高水平的)论述,并决定将其分为三个部分。在这第一篇文章中,我将对 Python 打包的工做方式及其所具备的打包类型进行大概介绍。在第二篇文章中,我将详细地介绍软件包的安装方式,以及 PEP-517/518 是如未尝试对其进行改进的。最后,我再专门写另外一篇文章,以介绍在引入这些改进时,咱们吸收的一些痛苦的教训。测试
事先声明,我将主要关注 Python 官方的打包系统(即 pip、setuptools,所以没有 conda 或特定于操做系统的打包程序)。ui
Marcus Cramer 摄/Unsplash--人们第一次凝视 Python 打包时的脸
为了讲这个故事,我须要先讲讲如何分发 Python 软件包的故事;更具体地说,包的安装在过去是如何运做的,以及咱们但愿它在未来如何运做。
为了有一个具体的示例,让我介绍一下个人很棒的示例库:pugs
。这个库至关简单:它只生成一个名为 pugs 的包,仅包含一个名为 logic 的模块。关于 pugs,你猜对了,logic 被用于生成随机的引号。这是一个展示为源码树(source tree)的简单示例结构(能够在gaborbernat / pugs 【2】里得到):
pugs-project ├── README.rst ├── setup.cfg ├── setup.py ├── LICENSE.txt ├── src │ └── pugs │ ├── __init__.py │ └── logic.py ├── tests │ ├── test_init.py │ └── test_logic.py ├── tox.ini └── azure-pipelines.yml
这里有四类独特的内容:
咱们的pugs
包在用户机器的解释器上能用,意味着什么?在理想状况下,一旦启动解释器,用户应该可以 import 它,并调用其中的函数:
业务逻辑代码(src 文件夹中的内容)
测试代码(tests 文件夹和 tox.ini)
包代码和元数据(setup.py、setup.cfg、LICENSE.txt、README.rst--请注意,咱们现在使用的是事实上的标准打包工具setuptools【3】)
有助于项目管理和维护的文件:
Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43) [Clang 6.0 (clang-600.0.57)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import pugs >>> pugs.do_tell() "An enlightened pug knows how to make the best of whatever he has to work with - A Pug's Guide to Dating - Gemma Correll"
Ryan Antooa 摄/Unsplash--让咱们开始吧,兴奋!
Python 怎么知道什么可用或不可用?简短的答案是,它不知道。至少不在前期知道。相反,它将尝试加载,并动态地检查是否可用。
它从哪里加载?有许多可能的位置,可是在大多数状况下,咱们说的是从文件系统的文件夹中加载。这个文件夹在哪里呢?对于给定的模块,能够打印该模块的表示(representation)来找出:
>>> import pugs >>> pugs <module 'pugs' from '/Users/bernat/Library/Python/3.7/lib/python/site-packages/pugs/__init__.py'>
你会发现文件夹的位置取决于:
可是通常来讲,对于给定的 Python 解释器,能够经过打印出 sys.path 变量的内容,来找到可能的目录列表,例如在个人 MacOS 上:
>>> import sys >>> print('\n'.join(sys.path)) /Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7 /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload /Users/bernat/Library/Python/3.7/lib/python/site-packages /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages
对于第三方软件包,会是一些 site-packages 文件夹。在以上示例中,请注意哪些是在整个系统范围内,哪些仅属于一个特定的用户。这些包是如何被放在此文件夹中的?它必定是由某些安装程序放在那里的。
下图展现了大多数的运行状况:
Pinho/摄--在探索新鲜事物
在安装时,软件包必须生成至少两种类型的内容,以放入 site-packages 中:有关软件包内容的元数据文件夹,其中包含 {package}-{version} .dist-info 和业务逻辑文件。
/Users/bgabor8/Library/Python/3.7/lib/python/site-packages/pugs ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── logic.cpython-37.pyc └── logic.py /Users/bgabor8/Library/Python/3.7/lib/python/site-packages/pugs-0.0.1.dist-info ├── INSTALLER ├── LICENSE.txt ├── METADATA ├── RECORD ├── WHEEL ├── top_level.txt └── zip-safe
发行信息(dist-info)文件夹描述了该软件包:用于安装该软件包的安装程序、该软件包所附的许可证、在安装过程当中建立的文件、顶层 Python 软件包是什么、该软件包暴露的入口等等。在PEP-427【6】 中能够找到每一个文件的详细说明。
咱们如何从源码树中得到这两种类型的内容呢?咱们面前有两条大相径庭的路径:
wheel
包。这两个方法的区别主要在于包的编译/构建操做发生在哪里:在开发者的计算机上仍是在终端用户的计算机上。若是它发生在开发者的一边(例如在 wheel 的状况下),则安装过程很是轻巧。一切都已经在开发机器上完成了。用户机器的操做仅是简单的下载和解压。
在本例中,咱们使用 setuptools 做为构建器(从源码树生成要放入 site-packages 文件夹中的内容)。所以,为了在用户机器上执行构建操做,咱们须要确保在用户机器上有合适版本的 setuptools (若是你使用的是 40.6.0 版的功能,则必须确保用户具备该版本或大于该版本)。
要考虑的另外一种状况是 Python 提供了从其内部访问 C/C++ 库的能力(在须要的地方得到额外的性能)。这样的软件包被称为 C 扩展包(C-extension packages),由于它们利用了 CPython 提供的 C 扩展 API。
此类扩展须要编译 C/C++ 功能,才能适用与其交互的 C/C++ 库和当前 Python 解释器的 C-API 库。在这些状况下,构建操做实际上涉及到调用一个二进制编译器,而不只仅是像纯 Python 包(例如咱们的 pugs 库)那样,生成元数据和文件夹结构。
若是在用户计算机上进行构建,则须要确保在构建时,有可用的正确的库和编译器。如今这是一项相对困难的工做,由于有些特定于平台的二进制文件,也是经过平台打包工具分发的。这些库的缺失或版本不匹配一般会在构建时触发隐秘的错误,使用户感到沮丧和困惑。
所以,若是可能的话,始终选择将 package 打包成 wheel。这将彻底避免用户缺乏正确的构建依赖项的问题(纯 Python 类型如 setuptools 或二进制类型的 C/C++ 编译器)。即便这些构建依赖项易于配置(例如,使用纯 Python 构建器--例如 setuptools),你彻底能够避免此步骤,来节省安装的时间。
话虽如此,仍然有两种须要提供源码分发的状况(即便在你提供 wheel 的状况下):
源码树(source tree)、源码分发(source distribution)和 wheel 之间的区别:
Charles PH 摄/Unsplash--hmmm
可在此阅读本系列的下一篇文章【7】,了解在安装软件包时会发生什么。谢谢阅读!
[1] The state of Python Packaging: https://www.bernat.tech/pep-517-and-python-packaging/
[2] gaborbernat / pugs: https://github.com/gaborbernat/pugs
[3] setuptools: https://pypi.org/project/setuptools
[4] PEP-370: https://www.python.org/dev/peps/pep-0370/
[5] https://pypi.org: https://pypi.org/
[6] PEP-427: https://www.python.org/dev/peps/pep-0427/%23id14#id14
[7] 下一篇文章: https://www.bernat.tech/pep-517-518/
公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写做、优质英文推荐与翻译等等,欢迎关注哦。