当前位置:   article > 正文

Python的setuptools详解【3】打包wheel并提交给pypi_将c++编译为wheel包

将c++编译为wheel包

一、说明

        上文说,如何用setup.py打包成egg包,继而又有find_package()函数用法,本篇专门介绍,如何用setuptools打包成不同的软件包。在这篇博客中,我们将使用 Setuptools 创建一个源代码发行版和 python 包的轮子。在转向我们如何打包之前。我们先来了解一下什么是源分发和轮子文件。

二、早期使用 Setup 构建 Python 包

        软件包是指可用于解决特定用例的可用单元或库。对于打包,我们有不同的构建工具,比如在 Scala 中我们有 SBT,或者在 JAVA 中我们有 maven 等。同样,我们在 python 中使用 Setuptools 来创建发行版。Source Distribution (sdist) 和 Wheels (whl) 是在 python 中创建包分发的两种主要方式。

2.1  源分配 ( sdist )

        除了源代码之外,它还包含额外的文件,如 CI 脚本、测试用例等,这些文件不需要为最终用户和最少使用的方法提供。这将以形式分发包,为不同的平台创建不同格式的存档。默认格式在 Linux 上是 gzip 压缩的 tar 文件 (),在 Windows 上是 ZIP 文件。.tar.gz

2.2 车轮 (whl)

         此发行版包含安装所需的源文件和包元数据。(.whl)

setup.py:

  1. 此文件包含 setup() 函数,我们在其中提供项目的特定详细信息。这包含包名称、版本、需要包、作者、描述、分类器等信息。
  2. 它是用于运行与打包任务相关的各种命令的命令行界面。如果您想更深入地了解,只需运行 python setup.py — 帮助

python 打包的图像结果

对于项目结构,我使用以下结构:

  1. dummy_project/
  2. |-- README
  3. |-- setup.py
  4. |-- example_pkg
  5. | |-- __init__.py
  6. | |-- calculate.py
  7. | |-- result.py
  8. |-- tests
  9. |-- |-- __init__.py
  10. |-- |-- run.py
  11. |-- |-- test.py

让我们创建最重要的文件 setup.py,它存在于项目目录的根目录中。

  1. from setuptools import setup
  2. setup(
  3. name = "dummy_project",
  4. version = "1.0.0",
  5. author = "dummy",
  6. author_email = "dummy@domain.com",
  7. description = ("This project helps to understand about setup.py."),
  8. url = "https://www.python.org/doc/",
  9. packages=['example_pkg'],
  10. python_requires='>=3.6.0'
  11. )

现在,让我们使用以下命令创建分配

python setup.py sdist bdist_wheel

这将生成两个发行版。现在,根据您的用例,您可以将包推送到公共或私有 python 存储库。可以通过 pip 命令安装,如下所示:

pip install package_name

在下一篇博客中,我们将学习如何执行 python 项目的自动版本控制。

参考资料
Python Packaging User Guide

Knoldus-blog-footer-image

        首先需要保证你有最新版的setuptools 和wheel

python -m pip install --user --upgrade setuptools wheel

三、使用Setuptools进行Python打包

本文将解释创建 Python 包的最佳实践。

它将涵盖:

  • 为什么我们要打包我们的代码
  • Python 打包的演变
  • Py脚手架
  • setup.cfg 中的有用配置
  • 构建并发布
  • 替代工具

摄影:Jiawei Zhao ,来自Unsplash

四、为什么我们要打包我们的代码?

Python 的主要优势之一是其庞大的包生态系统。开发人员只需简单的 pip 安装和导入即可轻松获得所有内容。包是一种使代码可重用且易于共享的方法。编写库而不是集合脚本会迫使您考虑设计和清晰的界面。

很多时候,一些代码被隐藏在某个存储库中。需要克隆它,安装需求,一般来说,使用起来不太方便。我们应该努力做得更好。

五、Python 打包的演变

5.1 requiements.txt

大多数新手都会从 和requirements.txt一些松散耦合的脚本开始他们的 Python 之旅。

  1. # 项目结构
  2. ├──requirements.txt
  3. ├──script_1.py
  4. └──script_2.py
  1. #requirements.txt
  2. numpy
  3. pandas==1.5.3
  1. # 安装
  2. pip install -rrequirements.txt

        它的简单性是主要吸引力,并且共享这些脚本可能很容易。只要这些脚本能够独立运行就可以了。但将它们集成到另一个项目中会很不方便。通常发生的情况是,人们会将您的脚本复制粘贴到他们的项目中,复制您的代码并进行自己的修改。

        如果你想让你的代码更加可重用,你需要编写一个库,打包并发布它。这样,每个人都可以直接安装并导入你的库。

        Python 打包因没有完整的文档而臭名昭著。做事没有确定的方法。即使是官方文档(没有中心位置)也可能包含过时的信息。随着时间的推移,许多不同的工作流程和工具已经发展。我不得不说,近年来文档已经有了很大的改进。那么如何创建一个包呢?

5.2 Distutils 与 setuptools

        人们可能经常会遇到这两个名字,一开始这让我很困惑。但这只是一个历史性的事情,Python 最初是作为distutils一个内置库,它是用于打包等的工具的集合。为了改进它,它setuptools被创建为一个可 pip 安装的包。今天,distutils根据PEP 632的决定,从 Python 3.10 版本开始正式弃用。

5.3 src 布局与平面布局

        在开始项目之前,您必须对项目结构做出决定。当我开始使用 Python 时,弄清楚项目结构是一个重大挑战。有多种方法可以做到这一点。当我阅读这篇文章时,这对我很有帮助,它确定了两个主要问题。有 src 布局和平面布局。

src 与平面布局

        两者之间的区别在于库代码的位置。在 src-layout 中,所有库代码都存储在 src 文件夹中,与所有其他样板文件(例如测试和文档)分开。这种清晰的分离对于自动化和包发现很有用。例如,您不会意外地发送样板代码。这样做的缺点是,在开发时,您需要进行可编辑的安装,因为 src 文件夹的额外误导,最终会出现在您的导入路径中。(例如仅import src.mypkg有效​​)

        在平面结构中,您可以直接执行代码(例如使用python -m package.script),因为所有内容都位于顶级文件夹中。但您需要更加小心地发现包。

        两种解决方案都是有效的,对于我们下面的示例,我们将专门使用 src-layout。

5.4 setup.py

        使用 setuptools,您可以拥有的最小设置是一个setup.py. 我们可以将依赖项移至requirements.txt参数中install_requires。此外,我们需要传递更多参数来考虑 src 样式布局。使用packagespackages_dir,我们src从导入路径中删除并指定所有包都位于src有关更多详细信息,您可以阅读我关于包发现的文章。

  1. # Project structure
  2. ├── setup.py
  3. └── src
  4. └── tutorial
  5. ├── __init__.py
  6. ├── script_1.py
  7. └── script_2.py
  1. # setup.py
  2. import setuptools
  3. from setuptools import setup
  4. setup(
  5. name="python_example",
  6. install_requires=[
  7. "numpy",
  8. "pandas==1.5.3",
  9. ],
  10. package_dir={"": "src"},
  11. packages = setuptools.find_packages(
  12. where='src',
  13. ),
  14. )
  1. # Editable install
  2. pip install -e .

这个设置可以完成工作。我们已经可以构建并发布这个包了。但是……这不是最佳实践。我们将在接下来的部分中找出原因。

5.5 setup.py + setup.cfg

        一个问题setup.py是它是可执行的。要读取元数据,setuptools 需要执行此脚本。对于自动化工具来说,它过于复杂且缓慢。例如,如果依赖解析器可以从机器可读的配置文件中读取依赖项,那么它会比执行 python 代码快得多。这就是setup.cfg引入的原因。

  1. # Project structure
  2. ├── setup.cfg
  3. ├── setup.py
  4. └── src
  5. └── tutorial
  6. ├── __init__.py
  7. ├── script_1.py
  8. └── script_2.py
  1. # setup.py
  2. from setuptools import setup
  3. setup()

所有可用的参数都setup.py可以在 中定义setup.cfg。当可用时,setup.py将自动读取, 。setup.cfg

  1. # setup.cfg
  2. [metadata]
  3. name = python_example
  4. [options]
  5. packages = find_namespace:
  6. include_package_data = True
  7. package_dir =
  8. =src
  9. [options.packages.find]
  10. where = src

        声明式setup.cfg更容易自动解析,并且不需要运行 Python 代码。在setup.py. 我的建议是,您从 开始setup.cfg,如果您发现达到了其局限性,则可以回退到setup.pypybind11是一个您必须依赖的示例来setup.py构建您的 C++ 代码。

5.6 构建依赖问题

        还有一个问题,又回到了setup.py。这是构建依赖问题:

  • setup.py如果不知道它的依赖关系就无法运行
  • 不运行就无法知道依赖关系setup.py

        这就是引入 PEP 518 的原因,用他们的话说:

[...] 当项目选择使用 setuptools 时,使用像 setup.py 这样的可执行文件就成为一个问题。在不知道其依赖项的情况下,您无法执行 setup.py 文件,但目前没有标准方法可以自动了解这些依赖项,而无需执行存储该信息的 setup.py 文件。这是一个第 22 条军规:如果不知道文件本身的内容,文件就无法运行,除非您运行该文件,否则无法以编程方式知道文件的内容。

资料来源: https: //peps.python.org/pep-0518/#rationale

解决方案是创建一个名为pyproject.toml.

5.7 setup.py + setup.cfg + pyproject.toml

  1. # 项目结构</span>
  2. ├── pyproject.toml
  3. ├── setup.cfg
  4. ├── setup.py
  5. └── src
  6. └──tutorial
  7. ├── __init__.py
  8. ├── script_1.py
  9. └── script_2.py
  1. [build-system]
  2. requires = ["setuptools>=46.1.0", "setuptools_scm[toml]>=5"]
  3. build-backend = "setuptools.build_meta"

        我们pyproject.toml可以指定构建系统的版本。在安装包的过程中,它将创建一个隔离的环境并安装正确的setuptools版本。

        pip install 根据 pyproject.toml 创建一个隔离的 venv

  setuptools只是众多构建后端之一。PEP 518标准化了构建后端的接口,使社区能够构建替代后端。

前端和后端,来源:https ://peps.python.org/pep-0517/

5.8 使用项目生成器:PyScaffold

        现在我们知道在启动项目时需要创建一堆文件。幸运的是,我们不必记住每一个细节。我们可以使用像PyScaffold这样的项目生成器。它为您建立了完美的项目结构并包含所有最佳实践。维护人员将了解最新的打包开发并相应地更新项目模板。

您可以简单地安装它:

  1. pip install pyscaffold
  2. putup python_example

pyscaffold 生成的文件

六、setup.cfg 中的有用配置

        现在我们已经有了正确的项目结构,我们将讨论一些经常使用的配置。

6.1 包裹名字

        您setup.cfg可以为您的包命名。这将确定 PyPI 下显示的名称,您将使用pip install <name>.

        安装程序.cfg

        我们必须在模块集合(Python 文件)中区分 PyPI 包和 Python 包。您用于导入代码的名称与 setup.cfg 名称无关,而是由 src 文件夹下的项目结构决定。

具有多个包的项目结构

导入名称

一个突出的例子是 opencv。您可以使用pip install opencv-pythonbut来安装它import cv2

6.2 版本

安装程序.cfg

版本控制遵循语义版本控制。简而言之:

  • 主要:不兼容的 API 更改
  • MINOR:添加功能,向后兼容
  • PATCH:向后兼容的错误修复

但大多数项目并没有严格遵守这些规则,您应该从用户的角度考虑什么是有意义的。

        最佳实践是,您应该在每次更改时升级版本,并且不应覆盖已经发布的包。您可能会引入重大更改,而其他人的构建管道将会中断。但他们将无法恢复到以前的工作版本,因为您已经覆盖了它。这将阻止他们,直到他们将代码库调整为您的新界面。对于第三方来说,恢复到以前的版本来修复他们的版本会更容易。然后,他们可以毫无压力地调整他们的代码以适应您的更改。

6.3 版本 - PyScaffold 陷阱

        当我使用 PyScaffold 时,有一个小烦恼我总是需要解决。默认项目模板将尝试从版本控制系统推断版本。这假设您每个存储库都有一个包。如果您有一个包含多个包的 monorepo,您​​将遇到此错误:

        运行: putup <package>

        运行: pip install -e 。

        要修复它,请按照下列步骤操作:

putup <package_name> --force

去掉红色部分

去掉红色部分

6.4 安装要求

安装程序.cfg

您可以在 下添加您的依赖项install_requires。您可以用来<=, ~=, >=, ==指定版本范围。

最佳实践是添加所有直接依赖项,即您在代码中导入的包。例如,numpy即使它带有,您也应该添加,因为如果您决定从项目中opencv删除,您的代码将会中断,因为不再有。opencvnumpy

另一个最佳实践是不要对依赖项版本过于严格。您可能倾向于使用最新版本的库,但由于存在许多依赖项,用户可能难以满足约束。如果有足够的依赖项,依赖项解析器将无法找到所有依赖项都兼容的星座。

6.5 额外要求

        太多的依赖,你很快就会发现自己陷入了依赖地狱。有时依赖项对于核心功能来说是可选的。就像运行时不需要的测试库一样。extras_requires您可以在名称下定义可选的依赖项。

安装程序.cfg

安装额外的依赖项

可编辑安装的语法

Horovod是一个分布式学习框架,支持多种深度学习框架。安装所有框架是不可能的,因此您可以选择一个带有extras_require.

如果您只使用包中的单个函数,则应该考虑自己实现它以减少依赖项的数量。

6.6 命名空间包

        命名空间包是本身不包含代码的包。它们是一种将包分组到一个命名空间下的方法。如果您想以同一名称发布多个包,这非常有用。它可以是您的伞式项目或组织的名称。这样,您还可以在不同存储库中处理不同的包,但仍然以相同的名称发布。例如,Azure有很多可以单独安装的包,但你可以像这样导入它们:

        天蓝色命名空间包

        PyScaffold 已经使用该指令默认创建了此行为find_namespace。您只需将包放在命名空间文件夹下即可。使用find_namespace,__init__.py不再需要,因为空文件夹也将被解析为包。

安装程序.cfg

项目结构

导入路径

6.7 入口点

        另一种有用的设置是entry_points. 它将在您的命令行中安装一个可执行文件。这对于创建 CLI 很有用。使用 PyScaffold,您只需取消注释骨架示例即可测试此功能。与 结合使用click,您可以立即创建自己的 CLI。

安装程序.cfg

命令行中可用的脚本

七、构建和发布

        当您完成包后,您应该考虑将其发布到 PyPI,要么发布到公共索引,要么考虑为您的内部解决方案使用公司托管的索引。

使用该build包来构建您的包:

构建命令

将创建两个工件:whlsdist

构建工件

Wheel (whl) 是包装代码的首选格式。您可以使用 检查内容unzip -l <package>.whl。轮子包含的文件格式可以通过将内容解压缩到站点包中来轻松安装。有关轮子的更多信息,请参见PEP 427

源代码分发版 (sdist) 更像是项目的副本。也可以安装它,但它更多地用于归档目的。

您可能会遇到一些仍然setup.py手动运行的教程。但这种行为已被弃用。

已弃用的构建和安装方式

最后一步是使用twine发布到 PyPI:

  1. <span style="color:rgba(0, 0, 0, 0.8)"><span style="background-color:#ffffff"><span style="background-color:#f9f9f9"><span style="color:#242424">pip install twine
  2. twine 上传 dist/*</span></span></span></span>

八、替代工具

我们之前介绍的是基本的包装堆栈:

  • 前端:
  • 后端:设置工具
  • 构建:构建
  • 发布: 麻绳

由于 Python 打包的坎坷历史,许多其他工具被创建来提供更好的体验。有很多,所以我只介绍一些值得注意的。

8.1 cookie切割机

如果您对 PyScaffold 项目模板不满意,可以使用Cookiecutter。Cookiecutter 是一个人们可以定义自己的可重用项目模板的项目。这个想法是创建一个cookiecutter,一旦你拥有它,你就可以用它来切出新的cookie(项目)。您可以使用其他人的模板,也可以作为高级用户创建您的个人模板。

8.2 Poetry(诗歌)

Poetry现在非常流行,它的灵感来自于 Rust 的 Cargo 包管理器。它取代了整个 pip、setuptools 等堆栈,提供了一个可以完成所有操作的命令行工具。

8.3 项目数据管理

        与诗歌类似,pdm 受到另一个包管理器的启发,即 npm。一个值得注意的功能是它支持PEP 582,这消除了 virtualenvs 的需要。对于 Python 来说,这是一个令人兴奋的新方向。也许根本不需要 virtualenvs 的概念。

更新: PEP 582 被拒绝。合理的理由是,影响模块搜索路径的方法已经太多了,与新用户获得的微小好处相比,再增加一种方法只会增加更多的复杂性。在这里阅读更多详细信息:PEP 582 - Python local packages directory - #429 by frostming - Packaging - Discussions on Python.org

九、最后的话

        包装的方法有很多种。我个人的建议是将 PyScaffold 与常规打包堆栈一起使用 - pip、setuptools、build、twine。如果您知道自己在做什么,请考虑 cookiecutter。或者,如果您想要获得更简化的体验并愿意进行一些实验,请选择 Poetry 和 PDM。我说的是实验性的,但它们已经很成熟并且在社区中受到好评。我们的梦想是有一天我们会有一种明确的做事方式,这样我们就不必再仔细比较不同的工具了。

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号