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

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

  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-5...

[2] gaborbernat / pugs: https://github.com/gaborberna...

[3] setuptools: https://pypi.org/project/setu...

[4] PEP-370: https://www.python.org/dev/pe...

[5] https://pypi.orghttps://pypi.org/

[6] PEP-427: https://www.python.org/dev/pe...

[7] 下一篇文章: https://www.bernat.tech/pep-5...

公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。

Image placeholder
qingcai
未设置
  24人点赞

没有讨论,发表一下自己的看法吧

推荐文章
三种类型的物联网平台分析

企业依靠其物联网平台提供许多服务。其中最重要的是分析。通俗地说,物联网分析是一门科学和艺术,它试图在连接资产生成的海量数据中找到模式。MachNation的物联网平台测试实验室给出更详尽地定义,分析是

适配器模式的三种形式

适配器模式,顾名思义,就是把原本不兼容的接口,通过适配,使之兼容。举个生活中简单的例子,以前的手机内存卡可以取出来,但是想和电脑之间传输音乐、视频等资料不能直接传输,需要通过USB读卡器,然后插入US

jquery中有哪几种类型的选择器?

jQuery选择器一、基本选择器基本选择器是jQuery中最常用也是最简单的选择器,它通过元素的id、class和标签名等来查找DOM元素。1、ID选择器#id描述:根据给定的id匹配一个元素,返回单

Python 教程-了解Python

什么是Python Python能干什么? 有什么特点? 什么是Python 官方介绍: Python是一个易于学习、功能强大的编程语言。它拥有高效高级的数据结构和一种简单有效的面向对象编程的

Python 教程-Python 安装

在Windows上安装 访问https://www.python.org/downloads/并下载最新版本。在撰写时当前最新是3.8。在安装的时候和其他软件一样,无脑式下一步。需要注意的是如果在W

Python入门教程_2. 使用 Python 解释器

2.1.调用解释器 Python解释器通常安装在目标机器上的/usr/local/bin/python3.7目录下;把/usr/local/bin目录放进你的Unixshell的搜索路径里,确保它可以

Python入门教程_3. Python 简介

在下面的例子中,输入和输出分别由大于号和句号提示符(>>>和...)标注:如果想重现这些例子,就要在解释器的提示符后,输入(提示符后面的)那些不包含提示符的代码行。需要注意的是在练习中遇到的从属提示符

Python入门教程_4. 深入 Python 流程控制

除了刚刚介绍的while语句,Python还有一些在其他语言中常见的控制流语句,并做了一些改动。 4.1.if语句 也许最著名的语句是if语句了。 例如: >>>x=int(input("Please

【python测试开发栈】帮你总结python random模块高频使用方法

随机数据在平时写python脚本时会经常被用到,比如随机生成0和1来控制逻辑、或者从列表中随机选择一个元素(其实抽奖程序也类似,就是从公司所有人中随机选择中奖用户)等等。这篇文章,就帮大家整理在pyt

流畅的Python读书笔记 --- 第一章 Python数据模型

近期开始读“流畅的Python”这本书,想把自己的读书笔记分享给大家,希望能帮到也对这本书感兴趣但是没时间看的各位。(文章中大部分的话和图片摘录总结自“流畅的Python”一书,以及python官方网

【python测试开发栈】帮你总结python time模块高频使用方法

在平时写python脚本时,时间是我们经常用到的数据,比如:时间戳、前端展示的对应格式的时间等,在python中主要有三个和时间处理相关的模块:time、datetime、calendar,这篇文章主

Python 打包——过去、现在与未来

英文|Pythonpackaging-Past,Present,Future【1】原作|BERNATGABOR译者|豌豆花下猫声明:本文获得原作者授权翻译,转载请保留原文出处,请勿用于商业或非法用途。

人脸脸型分类研究现状

今天给大家带来一篇人脸识别中的脸型识别,不同的脸型适合的眼镜发型不同,那么计算机要如何基于人脸图像来确定脸型呢? 01概述 人脸脸型就是指我们平时常说的,瓜子脸、圆脸、方脸等。人脸脸型分类就是算法根

Python教程-强制数据类型转换

数据类型转换分为两种,一种是自动数据类型转换(隐式)和强制数据类型转换(显式)原则上不同的数据之间无法进行运算,自动数据类型转换是程序自发的行为,比如在运算时(或判断时)。在需要的情况下进行的操作

python数据类型

1.string字符串类型 字符串是通过单引号、双引号或三引号进行定义的; 单、双引号定义字符串不能随意换行,需要在换行时指明换行符; 字符串中的引号可以互相嵌套,但不能嵌套自己(例如不能再单引号中

ROM 种类

ROM(Read-OnlyMemory),只读存储器。是一种只能读取所存数据的固态半导体存储器。通常用在不需经常变更资料的电子或电脑系统中,并且因为数据通过各种手段固化在设备上,所以资料不会因为电源关

go 数组传递给函数三种方式

funcmain(){ arr1:=[5]int{} //值传递 arr(arr1) fmt.Println(arr1) //&地址传递(指针) arra(&arr1) fmt.Println(

详解 PHP 中的三大经典模式

单例模式 单例模式的含义:作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统全局地提供这个实例。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。单例模式的三

详解 PHP 中的三大经典模式

单例模式 单例模式的含义:作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统全局地提供这个实例。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。单例模式的三

关于SD-WAN的三大误区

传统的广域网已经跟不上时代的发展速度。在宽带时代,网络专业人员在管理和配置分支机构时困难重重。员工注意到他们的关键业务应用程序的速度变慢了,而其他应用程序(如流媒体体育赛事)则会覆盖网络。拥有许多分支

ERP为什么要上云的三个理由!

很多企业之所以不想把ERP迁移到云端,是因为流程和数据是最大的壁垒。我们都知道,ERP是企业的关键业务系统,包含大量与业务相关的数据,因此很多企业迟迟不愿意上云,甚至干脆拒绝上云。但是,ERP迁云却是

关于强化学习你应该知道的三件事

如果您有在关注科技相关的新闻,可能读过有关人工智能(AI)应用程序如何通过强化学习训练,在围棋、国际象棋等棋类游戏以及电子游戏中击败人类玩家的报道。作为一名工程师、科学家或研究人员,您可能会希望利用这

超8千Star,火遍Github的Python反直觉案例集!

大数据文摘授权转载作者:SatwikKansal译者:暮晨Python,是一个设计优美的解释型高级语言,它提供了很多能让程序员感到舒适的功能特性。但有的时候,Python的一些输出结果对于初学者来说似

面向回家编程!GitHub标星两万的”Python抢票教程”,我们先帮你跑了一遍

盼望着,盼望着,春节的脚步近了,然而,每年到这个时候,最难的,莫过于一张回家的火车票。据悉,今年春运期间,全国铁路发送旅客人次同比将增长8.0%。达到4.4亿人次,2020年铁路春运自1月10日开始,

Go 语言数据类型:字符类型

字符类型 字符基本输入与输出 所谓字符类型是用单引号括起来的单个字符,关于字符的定义使用如下: funcmain(){ //声明变量 varchbyte//声明字符类型 chbyte='a'//单引号