参考翻译自官方文档:https://setuptools.readthedocs.io

开发人员指南

基本用法

  1. from setuptools import setup, find_packages
  2. setup(
  3. name="HelloWorld",
  4. version="0.1",
  5. packages=find_packages(),
  6. )

如您所见,在项目中使用setuptools并不需要花费太多时间。在您的项目文件夹以及您开发的Python包中运行该脚本。
调用该脚本生成发行版并自动包含setup.py所在目录中的所有包。请参阅下面的命令参考部分,了解可以向这个设置脚本提供哪些命令。例如,要生成一个源分发版,只需调用:

  1. setup.py sdist

当然,在将项目发布到PyPI之前,您需要向设置脚本中添加更多信息,以帮助人们找到或了解您的项目。 到那时,也许您的项目将会发展壮大,以包括一些依赖项,以及一些数据文件和脚本:

  1. from setuptools import setup, find_packages
  2. setup(
  3. name="HelloWorld",
  4. version="0.1",
  5. packages=find_packages(),
  6. scripts=["say_hello.py"],
  7. # Project uses reStructuredText, so ensure that the docutils get
  8. # installed or upgraded on the target machine
  9. install_requires=["docutils>=0.3"],
  10. package_data={
  11. # If any package contains *.txt or *.rst files, include them:
  12. "": ["*.txt", "*.rst"],
  13. # And include any *.msg files found in the "hello" package, too:
  14. "hello": ["*.msg"],
  15. },
  16. # metadata to display on PyPI
  17. author="Me",
  18. author_email="me@example.com",
  19. description="This is an Example Package",
  20. keywords="hello world example examples",
  21. url="http://example.com/HelloWorld/", # project home page, if any
  22. project_urls={
  23. "Bug Tracker": "https://bugs.example.com/HelloWorld/",
  24. "Documentation": "https://docs.example.com/HelloWorld/",
  25. "Source Code": "https://code.example.com/HelloWorld/",
  26. },
  27. classifiers=[
  28. "License :: OSI Approved :: Python Software Foundation License"
  29. ]
  30. # could also include long_description, download_url, etc.
  31. )

在接下来的部分中,我们将解释这些 setup() 参数的作用(元数据参数除外)以及在自己的项目中使用它们的各种方式。

新增和更改的 setup() 关键字

setup() 的以下关键字参数是由 setuptools 添加或更改的。它们都是可选的,您不必提供它们,除非您需要相关的 setuptools 特性。

  • name - 指定包名称的字符串。
  • version - 指定包版本号的字符串。
  • … …

使用 find_packages()

对于简单的项目,通常很容易手动将包添加到 setup() 的包参数中。然而,对于非常大的项目(Twisted、PEAK、Zope、Chandler等),保持包列表的更新是一个很大的负担。这时候可以使用 setuptools.find_packages() 来解决上述问题。
find_packages() 接受一个源目录和两个要排除和包含的包名称模式列表。如果省略,源目录默认与设置脚本所在的目录相同。有些项目使用 srclib 目录作为源树的根目录,这些项目当然会使用 srclib 作为 find_packages() 的第一个参数(这样的项目在 setup() 参数中还需要像 package dir={"": "src"} 这样的东西)。
无论如何, find_packages() 会遍历目标目录,通过包含的模式( * )进行过滤,并找到Python软件包(任何目录)。 仅当软件包包含 __init__.py 文件时,程序包才会被识别。 最后,应用排除模式以删除匹配的程序包。
包含和排除模式是程序包名称,可以选择包含通配符。 例如, find_packages(exclude=["*.tests"]) 将排除名称以 tests 结尾的所有软件包。 或者, find_packages(exclude=["*.tests", "*.tests.*"]) 也将排除名为 tests 的软件包的任何子软件包,但仍不排除顶级 tests 软件包或其子级。 实际上,如果您真的根本不需要测试包,则需要这样的这样:

  1. find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"])

为了覆盖所有情况。 实际上,排除模式旨在覆盖比这更简单的用例,比如排除单个指定的包及其子包。无论使用什么参数, find_packages() 函数都会返回适合用作 setup()packages 参数的软件包名称列表,因此这通常是在安装脚本中设置该参数的最简单方法。 尤其是因为它使您免于必须在项目增长其他顶级程序包或子程序包时记住修改设置脚本的麻烦。

find_namespace_packages()

在Python 3.3+中, setuptools 还提供了 find_namespace_packages 变体,它与 find_packages 具有相同的函数签名,但与符合 PEP 420 的隐式名称空间包一起工作。下面是一个使用 find_namespace_packages 的最小设置脚本:

  1. from setuptools import setup, find_namespace_packages
  2. setup(
  3. name="HelloWorld",
  4. version="0.1",
  5. packages=find_namespace_packages(),
  6. )

请记住,根据 PEP 420,您可能需要重新组织您的代码库,或者定义一些例外,因为隐式名称空间包的定义是非常宽松的,所以对于这样组织的项目来说:

  1. ├── namespace
  2. └── mypackage
  3. ├── __init__.py
  4. └── mod1.py
  5. ├── setup.py
  6. └── tests
  7. └── test_mod1.py

一个简单的 find_namespace_packages() 将安装两个名称空间。 mypackage 和一个名为 tests 的顶级包!避免这个问题的一种方法是使用 include 关键字将要包含的包列入白名单,就像这样:

  1. from setuptools import setup, find_namespace_packages
  2. setup(
  3. name="namespace.mypackage",
  4. version="0.1",
  5. packages=find_namespace_packages(include=["namespace.*"])
  6. )

另一个选择是使用 src 布局,其中所有软件包代码都放置在 src 目录中,如下所示:

  1. ├── setup.py
  2. ├── src
  3. └── namespace
  4. └── mypackage
  5. ├── __init__.py
  6. └── mod1.py
  7. └── tests
  8. └── test_mod1.py

在这种布局下,包目录指定为 src ,如下所示:

  1. setup(name="namespace.mypackage",
  2. version="0.1",
  3. package_dir={"": "src"},
  4. packages=find_namespace_packages(where="src"))

自动创建脚本

使用 distutils 打包和安装脚本可能会有些尴尬。 一方面,在Windows和POSIX平台上,没有一种简单的方法来使脚本的文件名与本地约定匹配。 另外,当您实际的 main 是某个模块中某个函数的功能时,通常必须为 main 脚本创建一个单独的文件。 即使在Python 2.4中,使用 -m 选项也仅适用于未安装在软件包中的实际 .py 文件。
setuptools 通过自动为您生成具有正确扩展名的脚本来解决所有这些问题,在 Windows 上,它甚至会创建一个 .exe 文件,这样用户就不必更改其PATHEXT设置。 使用此功能的方法是在安装脚本中定义 entry_points ,以指示所生成的脚本应导入并运行哪些功能。 例如,要创建两个名为 foobar 的控制台脚本和一个名为 baz 的GUI脚本,您可以执行以下操作:

  1. setup(
  2. # other arguments here...
  3. entry_points={
  4. "console_scripts": [
  5. "foo = my_package.some_module:main_func",
  6. "bar = other_module:some_func",
  7. ],
  8. "gui_scripts": [
  9. "baz = my_package_gui:start_func",
  10. ]
  11. }
  12. )

当此项目安装在非Windows平台上时(使用 setup.py installsetup.py developpip 进行安装),将安装一组 foobarbaz 脚本,这些脚本将从以下位置导入 main_funcsome_func 指定的模块。 您指定的函数将不带任何参数调用,并且它们的返回值将传递到 sys.exit() ,因此您可以返回错误级别或消息以打印到 stderr
在Windows上,将创建一组 foo.exebar.exebaz.exe 启动器,以及一组 foo.pybar.pybaz.pyw 文件。 .exe 包装器找到并执行正确版本的Python,以运行 .py.pyw 文件。
您可以根据需要定义任意多个“控制台脚本”和“gui脚本”入口点,并且每个入口点都可以选择指定其依赖的“扩展名”,在脚本运行时将其添加到 sys.path 中。 有关“extras”的更多信息,请参见下面的“声明其他内容”部分。 有关一般“入口点”的更多信息,请参阅下面的“动态发现服务和插件”部分。

声明依赖

  1. setup(
  2. ...
  3. dependency_links=[
  4. "http://peak.telecommunity.com/snapshots/"
  5. ],
  6. extras_require={
  7. "PDF": ["ReportLab>=1.2", "RXP"],
  8. "reST": ["docutils>=0.3"],
  9. }
  10. entry_points={
  11. "console_scripts": [
  12. "rst2pdf = project_a.tools.pdfgen [PDF]",
  13. "rst2html = project_a.tools.htmlgen",
  14. # more script entry points ...
  15. ],
  16. }
  17. )

包括数据文件

传统上,distutils 允许安装“数据文件”,这些文件放置在特定于平台的位置。 但是,随包一起分发的数据文件最常见的用例是由包使用,通常是通过将数据文件包括在包目录中来实现。
Setuptools提供了三种方法来指定要包含在软件包中的数据文件。 首先,您可以简单地使用 include_package_data 关键字,例如:

  1. from setuptools import setup, find_packages
  2. setup(
  3. ...
  4. include_package_data=True
  5. )

如果您希望对包含的文件进行更细粒度的控制(例如,如果您的软件包目录中有文档文件,并希望将其排除在安装范围之外),则也可以使用 package_data 关键字,例如:

  1. from setuptools import setup, find_packages
  2. setup(
  3. ...
  4. package_data={
  5. # If any package contains *.txt or *.rst files, include them:
  6. "": ["*.txt", "*.rst"],
  7. # And include any *.msg files found in the "hello" package, too:
  8. "hello": ["*.msg"],
  9. }
  10. )

package_data 参数是一个字典,它将包名从映射到 glob 模式列表。 如果数据文件包含在包的子目录中,则 globs 可能包含子目录名称。 例如,如果如下所示的包文件树:

  1. src/
  2. mypkg/
  3. __init__.py
  4. mypkg.txt
  5. data/
  6. somefile.dat
  7. otherdata.dat

setuptools的安装文件可能如下所示:

  1. from setuptools import setup, find_packages
  2. setup(
  3. ...
  4. packages=find_packages("src"), # include all packages under src
  5. package_dir={"": "src"}, # tell distutils packages are under src
  6. package_data={
  7. # If any package contains *.txt files, include them:
  8. "": ["*.txt"],
  9. # And include any *.dat files found in the "data" subdirectory
  10. # of the "mypkg" package, also:
  11. "mypkg": ["data/*.dat"],
  12. }
  13. )

请注意,如果您在 package_data 中的空字符串下列出了模式,则这些模式用于查找每个程序包中的文件,即使是那些也列出了自己的模式的文件。 因此,在上面的示例中, mypkg.txt 文件被包括在内,即使该文件未在 mypkg 的模式中列出。
还要注意,如果使用路径,则即使在Windows上,也必须使用正斜杠 / 作为路径分隔符。 在构建时,Setuptools 会自动将斜杠转换为适当的特定于平台的分隔符。
如果数据文件包含在一个本身不是包的包的子目录中(没有__init__.py ),则 package_data 参数中需要子目录名称(或 * )(如上 data/*.dat )。
生成 sdist 时,数据文件也是从 package_name.egg-info/SOURCES.txt 文件中提取的,因此,如果在调用 setup.py之前更新了 setup.pypackage_data 列表,请确保将其删除。
有时,仅 include_package_datapackage_data 选项不足以精确定义要包含的文件。 例如,您可能希望在版本控制系统和源发行版中包括程序包 README 文件,但将其排除在安装范围之外。
因此,setuptools 还提供了 exclude_package_data 选项,使您可以执行以下操作:

  1. from setuptools import setup, find_packages
  2. setup(
  3. ...
  4. packages=find_packages("src"), # include all packages under src
  5. package_dir={"": "src"}, # tell distutils packages are under src
  6. include_package_data=True, # include everything in source control
  7. # ...but exclude README.txt from all packages
  8. exclude_package_data={"": ["README.txt"]},
  9. )

exclude_package_data 选项是一个字典,它将包名称映射到通配符模式列表,就像 package_data 选项一样。
总之,三个选项使您可以:

  • include_package_data - 接受MANIFEST.in匹配的所有数据文件和目录。
  • package_data - 指定其他模式以匹配MANIFEST.in或源代码管理中可能不匹配的文件。
  • exclude_package_data - 指定安装软件包时不应包含的数据文件和目录的模式,即使由于使用上述选项而原本会包含这些模式也是如此。

    在运行时访问数据文件

    通常,现有的程序操作一个包的文件属性来找到数据文件的位置。但是,这种操作不兼容基于PEP 302的导入钩子,包括从zip文件和Python蛋中导入。强烈建议,如果您正在使用数据文件,那么应该使用pkg资源的ResourceManager API来访问它们。pkg资源模块是作为setuptools的一部分分发的,因此如果您正在使用setuptools来分发包,没有理由不使用它的资源管理API。请参阅Importlib资源以获得快速的了解

通常,现有程序会操纵包的 __file__ 属性,以查找数据文件的位置。 但是,此操作与基于PEP 302的 import hooks 不兼容,包括从 zip 文件和 Python Eggs 导入。 强烈建议,如果您正在使用数据文件,则应使用 pkg_resourcesResourceManager API 来访问它们。 pkg_resources 模块作为 setuptools 的一部分分发,因此,如果您使用 setuptools 分发软件包,则没有理由不使用其资源管理API。
有关将使用 __file__ 代替pkg_resources的代码转换的快速示例,另请参见 importlib.resources

Non-Package数据文件

过去,setuptools通过简单安装的方式将数据文件从发行版封装到egg中(参见以前的文档)。由于eggs不赞成使用,基于pip的安装会回到特定于平台的位置来安装数据文件,因此不支持可靠地检索这些资源的工具。
相反,PyPA建议将希望在运行时可访问的任何数据文件包含在包中。

自动资源提取

如果您正在使用的工具,期望你的资源真正的文件,或者你的项目包括non-extension本地库或其他文件,你的C扩展希望能够访问,你可能需要列出这些文件在急切的资源参数设置(),以便将提取的文件一起,每当一个C扩展项目中导入。
如果正在使用的工具期望您的资源是“真实”文件,或者您的项目包括 non-extension原生库或 C 扩展期望能够访问的其他文件,则可能需要在 eager_resources 参数中列出这些文件到 setup() ,以便在导入项目中的 C 扩展名时将文件一起提取。
如果您的项目包括 distutils 内置的 C 扩展名之外的共享库,并且这些共享库使用 .dll.so.dylib 以外的文件扩展名,这是特别重要的,这些扩展名是 setuptools 0.6a8及更高版本自动检测为共享库,并为您添加到 native_libs.txt 文件。 名称不以这些扩展名之一结尾的任何共享库都应列为 eager_resources ,因为使用链接到它们的 C 扩展名时,它们必须存在于文件系统中。
每当通过resource_filename() API请求任何 C 扩展名或eager_resources 时,压缩包的 pkg_resources 运行时将同时自动提取所有C扩展名和 eager_resources。 (C扩展名是在内部使用 resource_filename() 导入的。)确保 C 扩展名可以看到他们期望看到的所有“真实”文件。
还要注意,您也可以在 eager_resources 中列出目录资源名称,在这种情况下,每当请求任何 C 扩展名或eager_resources 时,目录的内容(包括子目录)都将被提取。
请注意,如果不确定是否需要使用此参数,则不必! 它的真正目的是支持具有许多非Python 依赖项的项目,并且是无法处理压缩的繁琐项目的不得已的选择。 如果您的软件包是纯 Python,Python 加上数据文件或 Python 加上 C,那么您确实不需要此软件包。 您必须使用 C 或需要在项目中使用“真实”文件的外部程序,然后eager_resources与项目相关。

动态发现服务和插件

  1. setup(
  2. # ...
  3. entry_points={"blogtool.parsers": ".rst = some_module:SomeClass"}
  4. )
  5. setup(
  6. # ...
  7. entry_points={"blogtool.parsers": [".rst = some_module:a_func"]}
  8. )
  9. setup(
  10. # ...
  11. entry_points="""
  12. [blogtool.parsers]
  13. .rst = some.nested.module:SomeClass.some_classmethod [reST]
  14. """,
  15. extras_require=dict(reST="Docutils>=0.3.5")
  16. )

开发模式

通常情况下,distutil 假设您要构建项目的发行版,而不是以原始或未构建的形式使用它。如果以这种方式使用 distutils,那么在开发期间每次对项目进行更改时,都必须重新构建并重新安装项目。
有时 distutils 会带来另一个问题,您可能需要同时在两个相关项目上进行开发。这样您可能需要将两个项目的程序包放在同一目录中以运行它们,但出于版本控制的目的,需要将它们分开。 你该怎么做?
Setuptools 允许您将项目部署到公共目录或暂存区域中使用,而无需要复制任何文件。因此,您可以在项目的签出目录中编辑它们各自的代码,并且仅在更改项目的C扩展名或类似编译的文件时才需要运行构建命令。如果这是您喜欢的工作方式,您甚至可以将一个项目部署到另一个项目的签出目录中(而不是使用一个公共的独立staging区域或 site-packages 目录)。
为此,请使用 setup.py develop 命令。 它的工作原理与 setup.py install 非常相似,除了它实际上并没有安装任何东西。 而是在部署目录中创建一个特殊的 .egg-link 文件,该文件链接到您的项目源代码。 而且,如果您的部署目录是Python 的 site-packages 目录,它还将更新 easy-install.pth 文件以包含项目的源代码,从而使 sys.path 中的所有使用该Python安装的程序都可以使用该文件。
另外, develop 命令在目标脚本目录中创建包装器脚本,该脚本将在确保所有 install.requires 软件包在 sys.path 上可用之后将运行开发中脚本。
您可以将同一项目部署到多个临时区域。例如,如果您在同一台计算机上有多个项目,它们共享一个项目,那么您在进行开发工作。完成给定的开发任务后,可以使用 setup.py develop --uninstall 从暂存区域中删除项目源,如果不是默认值,则指定所需的 staging 区域即可。
有多种选项可控制 develop 命令的精确行为。 有关更多详细信息,请参见下面的 develop 命令。

请注意,您还可以使用以下命令将setuptools命令应用于非setuptools项目:

  1. python -c "import setuptools; with open('setup.py') as f: exec(compile(f.read(), 'setup.py', 'exec'))" develop

也就是说,您可以简单地在引用的部分后面列出常规的设置命令和选项。

发布基于setuptools的项目

分发使用Cython编译的扩展

setuptools 会在构建时检测 Cython 是否被安装。如果没有找到 Cython , setuptools 将忽略 pyx 文件。要确保 Cython 可用,请在 pyproject.tomlbuild-requires 部分中包含 Cython

  1. [build-system]
  2. requires=[..., "cython"]

使用 pip 10 或更高版本构建时,该声明足以在构建中包含 Cython 。为了获得更广泛的兼容性,在 setup.cfgsetup-requires 中声明依赖项:

  1. [options]
  2. setup_requires =
  3. ...
  4. cython

只要 Cython 存在于构建环境中,只要扩展是使用 setuptools.Extension 定义的, setuptools 就包括对构建 Cython 扩展的透明支持。
如果遵循这些规则,就可以在设置脚本中将 .pyx 文件列为扩展对象的源文件。如果是这样,那么setuptools将使用它。
当然,要实现这一点,您的源代码发行版必须包括 Cython 生成的 C 代码以及原始的 .pyx 文件。这意味着您可能需要在修订控制系统中包含当前的 .c 文件,并重新构建它们。

使用 setup.cfg 文件配置 setup()

重要的是,如果希望与遗留构建(即那些不使用 PEP 517 构建API的构建)兼容,那么即使您的配置位于 setup.cfg 中,仍然需要一个 setup.py 文件,其中包含 setup() 函数调用。
Setuptools允许使用配置文件(通常是setup.cfg)定义包的元数据和通常提供给 setup() 函数(声明性配置)的其他选项。
这种方法不仅允许自动化方案,而且在某些情况下还减少了样板代码。

  1. [metadata]
  2. name = my_package
  3. version = attr: src.VERSION
  4. description = My package description
  5. long_description = file: README.rst, CHANGELOG.rst, LICENSE.rst
  6. keywords = one, two
  7. license = BSD 3-Clause License
  8. classifiers =
  9. Framework :: Django
  10. License :: OSI Approved :: BSD License
  11. Programming Language :: Python :: 3
  12. Programming Language :: Python :: 3.5
  13. [options]
  14. zip_safe = False
  15. include_package_data = True
  16. packages = find:
  17. scripts =
  18. bin/first.py
  19. bin/second.py
  20. install_requires =
  21. requests
  22. importlib; python_version == "2.6"
  23. [options.package_data]
  24. * = *.txt, *.rst
  25. hello = *.msg
  26. [options.extras_require]
  27. pdf = ReportLab>=1.2; RXP
  28. rest = docutils>=0.3; pack ==1.1, ==1.3
  29. [options.packages.find]
  30. exclude =
  31. src.subpackage1
  32. src.subpackage2
  33. [options.data_files]
  34. /etc/my_package =
  35. site.d/00_default.conf
  36. host.d/00_default.conf
  37. data = data/img/logo.png, data/svg/icon.svg

仅配置 setup.cfg

如果在调用 PEP 517 构建时项目目录中缺少 setup.py ,则 setuptools 将模拟一个仅包含一个 setuptools.setup() 调用的虚拟 setup.py 文件。
这意味着,如果您项目的前端构建总是与 PEP 517/PEP 518 兼容的话,则以没有 setup.py 文件,而只配置 setup.cfg
要使用此功能:

  • pyproject.toml 中指定构建需求和 PEP 517 构建后端。例如

    1. [build-system]
    2. requires = [
    3. "setuptools >= 40.9.0",
    4. "wheel",
    5. ]
    6. build-backend = "setuptools.build_meta"
  • 使用与 PEP 517 兼容的前端构建工具,如 pip>=19pep517

    src/ 布局

    一种常用的软件包配置将所有模块源代码都放在一个子目录(通常称为 src/ 布局)中,如下所示:

    1. ├── src
    2. └── mypackage
    3. ├── __init__.py
    4. └── mod1.py
    5. ├── setup.py
    6. └── setup.cfg

    您可以设置setup.cfg来自动查找子目录中的所有包,如下所示 ```python

    This example contains just the necessary options for a src-layout, set up

    the rest of the file as described above.

[options] package_dir= =src packages=find:

[options.packages.find] where=src ```

命令参考

扩展和重用 setuptools

创建distutils扩展

添加setup()的参数

自定义分发选项

添加新的EGG-INFO文件

添加对版本控制系统的支持

参考