社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python 打包的现状:包的三种类型

Python猫 • 6 年前 • 487 次点击  

👆 Python猫” ,一个值得加星标的公众号


英文 | The state of Python Packaging【1】

原作 | BERNAT GABOR
译者 | 豌豆花下猫
声明本文获得原作者授权翻译,转载请保留原文出处,请勿用于商业或非法用途。

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 或特定于操作系统的打包程序)。

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】)
  • 有助于项目管理和维护的文件:
  • - 持续集成(azure-pipelines.yml)
  • 版本控制(.git)
  • 项目管理(例如潜在的 .github 文件夹)
Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 201802: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 包的可用性

Python 怎么知道什么可用或不可用?简短的答案是,它不知道。至少不在前期知道。相反,它将尝试加载,并动态地检查是否可用。

它从哪里加载?有许多可能的位置,但是在大多数情况下,我们说的是从文件系统的文件夹中加载。这个文件夹在哪里呢?对于给定的模块,可以打印该模块的表示(representation)来找出:

>>> import pugs
>>> pugs
'pugs' from  '/Users/bernat/Library/Python/3.7/lib/python/site-packages/pugs/__init__.py'>
你会发现文件夹的位置取决于:
  • 软件包的类型(三方库或者标准库的内置/aka部分)

  • 它是全局的或仅限于当前的用户(请参阅PEP-370【4】)

  • 以及它是系统 Python 还是一个虚拟环境

但是一般来说,对于给定的 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 文件夹。在以上示例中,请注意哪些是在整个系统范围内,哪些仅属于一个特定的用户。这些包是如何被放在此文件夹中的?它一定是由某些安装程序放在那里的。

下图展示了大多数的运行情况:

  1. 开发者在文件夹(称为源码树)内编写一些 Python 代码。
  2. 然后,某些工具(例如 setuptools)将源码树打包以进行重新分发。
  3. 生成的软件包通过另一个工具(twine),上传到可以被终端用户计算机访问的中央存储仓(通常为https://pypi.org【5】)。
  4. 终端用户计算机使用一些安装程序来查找、下载和安装相关软件包。安装操作最终是在 site-packages 文件夹内,创建正确的目录结构和元数据。

Pinho/摄--在探索新鲜事物

Python 包的类型

在安装时,软件包必须生成至少两种类型的内容,以放入 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】中可以找到每个文件的详细说明。

我们如何从源码树中获得这两种类型的内容呢?我们面前有两条截然不同的路径:
  1. 从我们的源码树生成此目录结构和元数据,将其压缩为单个文件,然后将其发布到中央软件包存储仓。 在这种情况下,安装程序必须下载软件包并将其解压到 site-packages 文件夹中。我们将这种类型的包称为 wheel 包。
  2. 或者,你可以创建一个包含软件包源码的归档文件,构建所需的脚本和元数据,以生成可安装的(installable)目录结构,然后将其上传到中央存储仓。这称为源码分发或 sdist。在这种情况下,安装程序还有很多工作要做,它需要解压归档文件,运行构建器,然后再将其复制。

这两个方法的区别主要在于包的编译/构建操作发生在哪里:在开发者的计算机上还是在终端用户的计算机上。如果它发生在开发者的一边(例如在 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 的情况下):
  1. C 扩展的源码分发往往更易于审核,因为人们可以阅读源代码,从而在其内容上有更高的透明度:许多大型公司的环境出于此单一原因,更倾向于使用 wheel(它们通常会将此扩展到纯 Python wheel,主要是为了避免对哪些是纯 Python 和什么不是做分类)。
  2. 你可能无法为每个可能的平台都提供一个 wheel(在使用 C 扩展包的情况下,尤其如此),在这种情况下,源码分发可以让这些平台自行生成 wheel。

小结

源码树(source tree)、源码分发(source distribution)和 wheel 之间的区别:
  • 源码树——包含在开发者的机器/存储仓上可用的所有项目文件(业务逻辑、测试、打包数据、CI 文件、IDE 文件、SVC 等),例如,请参见上面的示例项目。
  • 源码分发——包含构建 wheel 所需的代码文件(业务逻辑+打包数据+通常还包括单元测试文件,用于校验构建;但是不包含开发者环境的内容,例如 CI/IDE/版本控制文件),格式:pugs-0.0 .1.tar.gz。
  • wheel——包含包的元数据和源码文件,被放到 site packages 文件夹,格式:pugs-0.0.1-py2.py3-NONE-any.whl。
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 命令行之旅:深入 click 之选项篇

Python 进阶:浅析「垃圾回收机制」

当谈论迭代器时,我谈些什么?

Python猫荐书之六:文也深度学习,理也深度学习

感谢创作者的好文
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/52420