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 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 包的可用性
Python 怎么知道什么可用或不可用?简短的答案是,它不知道。至少不在前期知道。相反,它将尝试加载,并动态地检查是否可用。
它从哪里加载?有许多可能的位置,但是在大多数情况下,我们说的是从文件系统的文件夹中加载。这个文件夹在哪里呢?对于给定的模块,可以打印该模块的表示(representation)来找出:
>>> import pugs
>>> pugs
<module '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 文件夹。在以上示例中,请注意哪些是在整个系统范围内,哪些仅属于一个特定的用户。这些包是如何被放在此文件夹中的?它一定是由某些安装程序放在那里的。
下图展示了大多数的运行情况:
- 开发者在文件夹(称为源码树)内编写一些 Python 代码。
- 然后,某些工具(例如 setuptools)将源码树打包以进行重新分发。
- 生成的软件包通过另一个工具(twine),上传到可以被终端用户计算机访问的中央存储仓(通常为https://pypi.org【5】)。
- 终端用户计算机使用一些安装程序来查找、下载和安装相关软件包。安装操作最终是在 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】 中可以找到每个文件的详细说明。
我们如何从源码树中获得这两种类型的内容呢?我们面前有两条截然不同的路径:
- 从我们的源码树生成此目录结构和元数据,将其压缩为单个文件,然后将其发布到中央软件包存储仓。在这种情况下,安装程序必须下载软件包并将其解压到 site-packages 文件夹中。我们将这种类型的包称为
wheel
包。 - 或者,你可以创建一个包含软件包源码的归档文件,构建所需的脚本和元数据,以生成可安装的(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 的情况下):
- C 扩展的源码分发往往更易于审核,因为人们可以阅读源代码,从而在其内容上有更高的透明度:许多大型公司的环境出于此单一原因,更倾向于使用 wheel(它们通常会将此扩展到纯 Python wheel,主要是为了避免对哪些是纯 Python 和什么不是做分类)。
- 你可能无法为每个可能的平台都提供一个 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猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。
Python 打包的现状:包的三种类型的更多相关文章
- Maven打jar包的三种方式
Maven打jar包的三种方式 不包含依赖jar包 该方法打包的jar,不包含依赖的jar包,也没有指定入口类. <build> <plugins> <plugin> ...
- python每次处理一个字符的三种方法
python每次处理一个字符的三种方法 a_string = "abccdea" print 'the first' for c in a_string: print ord(c) ...
- python对mysql数据库操作的三种不同方式
首先要说一下,在这个暑期如果没有什么特殊情况,我打算用python尝试写一个考试系统,希望能在下学期的python课程实际使用,并且尽量在此之前把用到的相关技术都以分篇博客的方式分享出来,有想要交流的 ...
- 命令行运行Python脚本时传入参数的三种方式
原文链接:命令行运行Python脚本时传入参数的三种方式(原文的几处错误在此已纠正) 如果在运行python脚本时需要传入一些参数,例如gpus与batch_size,可以使用如下三种方式. pyth ...
- 链路层三种类型的MAC地址
若需要转载,请注明出处. 我们知道,链路层都是以MAC地址来进行通信双方的地址标识的,如下图:在应用中根据接收方的多寡来进行划分,可分为以下三种: 单播(Unicast) 多播(Multicast) ...
- matlab for循环的三种类型
学习了一半了,发现一个好网站,就是我想写这篇博客用的,网络真是个好东西!纪念下国庆啦 网址:http://www.yiibai.com/matlab/matlab_for_loop.html ---- ...
- C# enum、int、string三种类型互相转换
enum.int.string三种类型之间的互转 #代码: public enum Sex { Man=, Woman= } public static void enumConvert() { in ...
- 缓慢变化维 (Slowly Changing Dimension) 常见的三种类型及原型设计(转)
开篇介绍 在从 OLTP 业务数据库向 DW 数据仓库抽取数据的过程中,特别是第一次导入之后的每一次增量抽取往往会遇到这样的问题:业务数据库中的一些数据发生了更改,到底要不要将这些变化也反映到数据仓库 ...
- ASP.NET 设计模式分为三种类型
设计模式分为三种类型,共23类. 一.创建型模式:单例模式.抽象工厂模式.建造者模式.工厂模式.原型模式. 二.结构型模式:适配器模式.桥接模式.装饰模式.组合模式.外观模式.享元模式.代 ...
随机推荐
- 从零学React Native之02状态机
本篇文章首发于简书 欢迎关注 之前我们介绍了RN相关的知识: 是时候了解React Native了 从零学React Native之01创建第一个程序 本篇文章主要介绍下下面的知识: 1.简单界面的搭 ...
- Namenode文件系统命名空间映像文件及修改日志
- Pytorch源码与运行原理浅析--网络篇(一)
前言 申请的专栏开通了,刚好最近闲下来了,就打算开这个坑了hhhhh 第一篇就先讲一讲pytorch的运行机制好了... 记得当时刚刚接触的时候一直搞不明白,为什么自己只是定义了几个网络,就可以完整的 ...
- 【Vue】基于nodejs的vue项目打包编译部署
一·项目编译 1·进入项目目录下的终端执行命令 npm run build 正常情况如下图,如遇到错误不会编译成功,且编译后的html文件不能正常渲染. 2·编译完成后进入项目下的dist目录运行生成 ...
- 在SuperSocket中启用TLS/SSL传输层加密
关键字: TLS, SSL, 传输层加密, 传输层安全, 证书使用, X509Certificate SuperSocket 支持传输层加密(TLS/SSL) SuperSocket 有自动的对TLS ...
- jq杂项方法/工具方法----trim() html() val() text() attr()
https://www.cnblogs.com/sandraryan/ $.trim() 函数用于去除字符串两端的空白字符.在中间的时候不会去掉. var str = ' 去除字符串左右两端的空格,换 ...
- Java反射机制(四):动态代理
一.静态代理 在开始去学习反射实现的动态代理前,我们先需要了解代理设计模式,那何为代理呢? 代理模式: 为其他对象提供一种代理,以控制对这个对象的访问. 先看一张代理模式的结构图: 简单的理解代理设计 ...
- NLP --- 条件随机场CRF详解 重点 特征函数 转移矩阵
上一节我们介绍了CRF的背景,本节开始进入CRF的正式的定义,简单来说条件随机场就是定义在隐马尔科夫过程的无向图模型,外加可观测符号X,这个X是整个可观测向量.而我们前面学习的HMM算法,默认可观测符 ...
- Vue 路由的嵌套使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- P1084 骑士的工作
题目描述 你作为一个村的村长,保卫村庄是理所当然的了.今天,村庄里来了一只恶龙,他有n个头,恶龙到处杀人放火.你着急了.不过天无绝人之路,现在来了一个骑士团.里面有m位成员,每个人都可以砍掉一个大小不 ...