Skip to content

Latest commit

 

History

History
802 lines (585 loc) · 30.1 KB

File metadata and controls

802 lines (585 loc) · 30.1 KB

十五、打包——创建自己的库或应用

到目前为止,本章已经介绍了如何编写、测试和调试 Python 代码。所有这些,只剩下一件事,那就是打包和分发 Python 库/和应用。为了创建可安装的软件包,我们将使用setuptools软件包,该软件包最近与 Python 捆绑在一起。如果您以前创建过包,您可能还记得distributedistutils2,但非常重要的是要记住,它们都已被setuptoolsdistutils所取代,您不应该再使用它们了!

setuptools可以打包哪些类型的程序?我们将向您展示几个案例:

  • 普通包裹
  • 数据包
  • 安装可执行文件和自定义setuptools命令
  • 在包上运行测试
  • 包含 C/C++扩展的包

安装包装

在我们真正开始之前,了解如何正确安装软件包是很重要的。安装软件包至少有四种不同的选项。第一个也是最明显的是使用普通的pip命令:

pip install package

这也可以通过直接使用setup.py来实现:

cd package
python setup.py install

这将在您的 Python 环境中安装该包,如果您正在使用它,则可能是virtualenv/venv,否则可能是全局环境。

但是,对于开发而言,不建议这样做。要测试代码,您需要为每次测试重新安装软件包,或者修改 Python 的site-packages目录中的文件,这意味着它也将在您的版本控制系统之外。这就是开发安装的意义所在;他们不需要将包文件复制到 Python 包目录,只需在site-packages目录中安装一个指向包实际所在路径的链接。这允许您修改代码并立即在运行的脚本和应用中看到结果,而无需在每次更改后重新安装代码。

与常规安装一样,pipsetup.py版本均可用:

pip installe package_directory

以及setup.py版本:

cd package_directory
python setup.py develop

设置参数

前几章实际上已经向我们展示了几个例子,但让我们重申并回顾一下最重要的部分实际上做了什么。您将在本章中使用的核心功能是setuptools.setup

对于最简单的包,与 Python 捆绑的distutils包也将足够了,但我还是推荐setuptoolssetuptools包具有distutils所缺乏的许多重要功能,几乎所有 Python 环境都将有setuptools可用。

在继续之前,请确保您拥有pipsetuptools的最新版本:

pip install -U pip setuptools

setuptoolsdistutils包在过去几年发生了重大变化,2014 年之前编写的文档/示例很可能已经过时。注意不要实施不推荐的示例,并使用distutils跳过任何文档/示例。

现在我们已经具备了所有的先决条件,让我们创建一个示例,其中包含最重要的内联文档字段:

import setuptools

if __name__ == '__main__':
    setuptools.setup(
        name='Name',
        version='0.1',

        # This automatically detects the packages in the specified
        # (or current directory if no directory is given).
        packages=setuptools.find_packages(),

        # The entry points are the big difference between
        # setuptools and distutils, the entry points make it
        # possible to extend setuptools and make it smarter and/or
        # add custom commands.
        entry_points={

            # The following would add: python setup.py
            # command_name
            'distutils.commands': [
                'command_name = your_package:YourClass',
            ],

            # The following would make these functions callable as
            # standalone scripts. In this case it would add the
            # spam command to run in your shell.
            'console_scripts': [
                'spam = your_package:SpamClass',
            ],
        },

        # Packages required to use this one, it is possible to
        # specify simply the application name, a specific version
        # or a version range. The syntax is the same as pip
        # accepts.
        install_requires=['docutils>=0.3'],

        # Extra requirements are another amazing feature of
        # setuptools, it allows people to install extra
        # dependencies if you are interested. In this example
        # doing a "pip install name[all]" would install the
        # python-utils package as well.
        extras_requires={
            'all': ['python-utils'],
        },

        # Packages required to install this package, not just for
        # running it but for the actual install. These will not be
        # installed but only downloaded so they can be used during
        # the install. The pytest-runner is a useful example:
        setup_requires=['pytest-runner'],

        # The requirements for the test command. Regular testing
        # is possible through: python setup.py test The Pytest
        # module installs a different command though: python
        # setup.py pytest
        tests_require=['pytest'],

        # The package_data, include_package_data and
        # exclude_package_data arguments are used to specify which
        # non-python files should be included in the package. An
        # example would be documentation files.  More about this
        # in the next paragraph
        package_data={
            # Include (restructured text) documentation files from
            # any directory
            '': ['*.rst'],
            # Include text files from the eggs package:
            'eggs': ['*.txt'],
        },

        # If a package is zip_safe the package will be installed
        # as a zip file. This can be faster but it generally
        # doesn't make too much of a difference and breaks
        # packages if they need access to either the source or the
        # data files. When this flag is omitted setuptools will
        # try to autodetect based on the existance of datafiles
        # and C extensions. If either exists it will not install
        # the package as a zip. Generally omitting this parameter
        # is the best option but if you have strange problems with
        # missing files, try disabling zip_safe.
        zip_safe=False,

        # All of the following fileds are PyPI metadata fields.
        # When registering a package at PyPI this is used as
        # information on the package page.
        author='Rick van Hattem',
        author_email='wolph@wol.ph',

        # This should be a short description (one line) for the
        # package
        description='Description for the name package',

        # For this parameter I would recommend including the
        # README.rst

        long_description='A very long description',
        # The license should be one of the standard open source
        # licenses: https://opensource.org/licenses/alphabetical
        license='BSD',

        # Homepage url for the package
        url='https://wol.ph/',
    )

这是相当多的代码和注释,但它涵盖了现实生活中遇到的大多数选项。这里讨论的最有趣、最通用的参数将在以下各节中分别介绍。

其他文档可以在pipsetuptools文档以及 Python 打包用户指南中找到:

包装

在我们的示例中,我们仅使用packages=setuptools.find_packages()。在大多数情况下,这将很好地工作,但重要的是要了解它的作用。find_packages函数查看给定目录中的所有目录,如果其中有__init__.py文件,则将其添加到列表中。所以你可以用['your_package']来代替find_packages()来代替。但是,如果您有多个软件包,这往往会变得单调乏味。这就是find_packages()有用的地方;只需指定一些包含参数(第二个参数)或一些排除参数(第三个参数),您的项目中就会有所有相关的包。例如:

packages = find_packages(exclude=['tests', 'docs'])

入境点

entry_points参数可以说是setuptools最有用的特性。它允许您为setuptools中的许多内容添加挂钩,但最有用的两个是可以同时添加命令行和 GUI 命令,并扩展setuptools命令。命令行和 GUI 命令甚至将在 Windows 上转换为可执行文件。第一节中的示例已经演示了这两个功能:

entry_points={
    'distutils.commands': [
        'command_name = your_package:YourClass',
    ],
    'console_scripts': [
        'spam = your_package:SpamClass',
    ],
},

本演示仅显示如何调用函数,但不显示实际函数。

创建全局命令

第一,一个简单的例子,没有什么特别之处;只是一个作为常规main函数调用的函数,您需要自己指定sys.argv(或者更好,使用argparse。这是setup.py文件:

import setuptools

if __name__ == '__main__':
    setuptools.setup(
        name='Our little project',
        entry_points={
            'console_scripts': [
                'spam = spam.main:main',
            ],
        },
    )

当然,这是spam/main.py文件:

import sys

def main():
    print('Args:', sys.argv)

确保不要忘记创建一个spam/__init__.py文件。它可以是空的,但它必须存在,以便 Python 知道它是一个包。

现在,让我们通过安装软件包来尝试一下:

# pip install -e .
Installing collected packages: Our-little-project
 Running setup.py develop for Our-little-project
Successfully installed Our-little-project
# spam 123 abc
Args: ['~/envs/mastering_python/bin/spam', '123', 'abc']

看看创建一个安装在常规命令行 shell 中的spam命令是多么容易!在 Windows 上,它实际上会为您提供一个可执行文件,该文件将添加到您的路径中,但无论平台如何,它都将作为一个单独的可调用可执行文件。

自定义 setup.py 命令

编写自定义setup.py命令可能非常有用。一个例子是我在所有包中使用的sphinx-pypi-upload-2,它是未维护的sphinx-pypi-upload包的叉。这个软件包使得构建 Sphinx 文档并将其上传到 Python 软件包索引变得非常简单,这在分发软件包时非常有用。使用sphinx-pypi-upload-2包,您可以执行以下操作(我在分发我维护的任何包时都会执行此操作):

python setup.py sdist bdist_wheel upload build_sphinx upload_sphinx

这个命令构建您的包并将其上载到 PyPI,构建 Sphinx 文档并将其上载到 PyPI。

当然,你想看看这是怎么回事。首先,这是我们spam命令的setup.py

import setuptools

if __name__ == '__main__':
    setuptools.setup(
        name='Our little project',
        entry_points={
            'distutils.commands': [
                'spam = spam.command:SpamCommand',
            ],
        },
    )

第二,SpamCommand类。基本要素是继承setuptools.Command并确保实现所有需要的方法。请注意,所有这些都需要实现,但如果需要,可以保留为空。以下是spam/command.py文件:

import setuptools

class SpamCommand(setuptools.Command):
    description = 'Make some spam!'
# Specify the commandline arguments for this command here. This
# parameter uses the getopt module for parsing'
    user_options = [
        ('spam=', 's', 'Set the amount of spams'),
    ]

    def initialize_options(self):
# This method can be used to set default values for the
# options. These defaults can be overridden by
# command-line, configuration files and the setup script
# itself.
        self.spam = 3

    def finalize_options(self):
# This method allows you to override the values for the
# options, useful for automatically disabling
# incompatible options and for validation.
        self.spam = max(0, int(self.spam))

    def run(self):
        # The actual running of the command.
        print('spam' * self.spam)

执行它非常简单:

# pip install -e .
Installing collected packages: Our-little-project
 Running setup.py develop for Our-little-project
Successfully installed Our-little-project-0.0.0
# python setup.py --help-commands
[...]
Extra commands:
 [...]
 spam              Make some spam!
 test              run unit tests after in-place build
 [...]

usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
 or: setup.py --help [cmd1 cmd2 ...]
 or: setup.py --help-commands
 or: setup.py cmdhelp

# python setup.py --help spam
Common commands: (see '--help-commands' for more)

[...]

Options for 'SpamCommand' command:
 --spam (-s)  Set the amount of spams

usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
 or: setup.py --help [cmd1 cmd2 ...]
 or: setup.py --help-commands
 or: setup.py cmd --help

# python setup.py spam
running spam
spamspamspam
# python setup.py spam -s 5
running spam
spamspamspamspamspam

很少有需要自定义setup.py命令的情况,但该示例仍然很有用,因为它目前是setuptools的未记录部分。

包装数据

在大多数情况下,您可能不需要包含包数据,但在确实需要数据与包一起使用的情况下,有几个不同的选项。首先,了解默认情况下包中包含哪些文件很重要:

  • 包目录中的 Python 源文件递归
  • setup.pysetup.cfg文件
  • 测试:test/test*.py
  • examples目录中的所有*.txt*.py文件
  • 根目录中的所有*.txt文件

在默认值之后,我们有了第一个解决方案:setup 函数的package_data参数。其语法非常简单,一个字典,其中键是包,值是要包含的模式:

package_data = {
    'docs': ['*.rst'],
}

第二种解决方案是使用MANIFEST.in文件。此文件包含要包括、排除等的模式。includeexclude命令使用模式进行匹配。这些模式是全局模式(参见glob模块获取文档:https://docs.python.org/3/library/glob.html )和包含和排除命令有三种变体:

  • include/exclude:这些命令只对给定的路径有效,没有其他作用
  • recursive-include/recursive-exclude:这些命令与include/exclude命令类似,但递归处理给定路径
  • global-include/global-exclude:对这些文件要非常小心,它们会在源代码树中的任何位置包含或排除这些文件

除了include/exclude命令外,还有另外两个命令;graftprune命令,包括或排除包含给定目录下所有文件的目录。这对于测试和文档非常有用,因为它们可以包含非标准文件。除了这些示例之外,显式地包含所需的文件并忽略所有其他文件几乎总是更好的。下面是一个例子MANIFEST.in

# Comments can be added with a hash tag
include LICENSE CHANGES AUTHORS

# Include the docs, tests and examples completely
graft docs
graft tests
graft examples

# Always exclude compiled python files
global-exclude *.py[co]

# Remove documentation builds
prune docs/_build

测试包

第 10 章测试和日志记录——为 bug 做准备、测试章节中,我们看到了许多针对 Python 的测试系统中的一些。正如你可能怀疑的那样,至少其中一些具有setup.py集成。

单元测试

在开始之前,我们应该为我们的包创建一个测试脚本。对于实际测试,请参阅第 10 章测试和日志记录——为 bug 做准备、测试章节。在这种情况下,我们将只使用无操作测试,test.py

import unittest

class Test(unittest.TestCase):

    def test(self):
        pass

标准的python setup.py test命令将运行常规的unittest命令:

# python setup.py -v test
running test
running "unittest --verbose"
running egg_info
writing Our_little_project.egg-info/PKG-INFO
writing dependency_links to Our_little_project.egg-info/dependency_links.txt
writing top-level names to Our_little_project.egg-info/top_level.txt
writing entry points to Our_little_project.egg-info/entry_points.txt
reading manifest file 'Our_little_project.egg-info/SOURCES.txt'
writing manifest file 'Our_little_project.egg-info/SOURCES.txt'
running build_ext
test (test.Test) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

可以告诉setup.py使用的--test-module--test-suite--test-runner参数使用不同的测试。虽然这些命令很容易使用,但我建议跳过常规的test命令,转而尝试nosepy.test

py 试验

py.test包有几种集成方法:pytest-runner、您自己的测试命令,以及不推荐的生成runtests.py脚本进行测试的方法。如果您的某个软件包仍在使用runtests.py,我强烈建议您切换到其他选项之一。

但是在我们讨论其他选项之前,让我们先确定我们有一些测试。因此,让我们在包中创建一个测试。我们将其存储在test_pytest.py中:

def test_a():
    pass

def test_b():
    pass

现在,其他测试选项。由于自定义命令实际上并没有添加太多内容,实际上使事情变得更复杂,因此我们将跳过它。如果要自定义测试的运行方式,请使用pytest.inisetup.cfg文件。最好的选择是pytest-runner,它使运行测试成为一项琐碎的任务:

# pip install pytest-runner
Collecting pytest-runner
 Using cached pytest_runner-2.7-py2.py3-none-any.whl
Installing collected packages: pytest-runner
Successfully installed pytest-runner-2.7
# python setup.py pytest
running pytest
running egg_info
writing top-level names to Our_little_project.egg-info/top_level.txt
writing dependency_links to Our_little_project.egg-info/dependency_links.txt
writing entry points to Our_little_project.egg-info/entry_points.txt
writing Our_little_project.egg-info/PKG-INFO
reading manifest file 'Our_little_project.egg-info/SOURCES.txt'
writing manifest file 'Our_little_project.egg-info/SOURCES.txt'
running build_ext
======================== test session starts =========================
platform darwin -- Python 3.5.1, pytest-2.8.7, py-1.4.31, pluggy-0.3.1
rootdir: h15, inifile: pytest.ini
collected 2 items

test_pytest.py ..

====================== 2 passed in 0.01 seconds ======================

为了正确地集成此方法,我们应该对脚本进行一些更改。它们不是严格需要的,但它使使用您的软件包的其他人更加方便,例如,可能不知道您正在使用py.test,的其他人。首先,我们通过修改setup.cfg来确保标准python setup.py test命令实际运行pytest命令:

[aliases]
test=pytest

其次,我们希望确保setup.py命令安装运行py.test测试所需的包。为此,我们还需要修改setup.py

import setuptools

if __name__ == '__main__':
    setuptools.setup(
        name='Our little project',
        entry_points={
            'distutils.commands': [
                'spam = spam.command:SpamCommand',
            ],
        },
        setup_requires=['pytest-runner'],
        tests_require=['pytest'],
    )

这种方法的优点在于,常规的python setup.py test命令可以工作,并且在运行测试之前自动安装所有需要的需求。但是,由于pytest要求仅在tests_require部分中,如果测试命令未运行,则不会安装它们。唯一会一直安装的软件包是pytest-runner软件包,这是一个非常轻的软件包,因此安装和运行起来非常轻。

鼻测试

nose软件包仅处理安装,与py.test略有不同。唯一的区别是py.test有一个单独的pytest-runner包用于测试跑步者,而 nose 包有一个内置的nosetests命令。因此,不用多说,这里是 nose 版本:

# pip install nose
Collecting nose
 Using cached nose-1.3.7-py3-none-any.whl
Installing collected packages: nose
Successfully installed nose-1.3.7
# python setup.py nosetests
running nosetests
running egg_info
writing top-level names to Our_little_project.egg-info/top_level.txt
writing entry points to Our_little_project.egg-info/entry_points.txt
writing Our_little_project.egg-info/PKG-INFO
writing dependency_links to Our_little_project.egg-info/dependency_lin
ks.txt
reading manifest file 'Our_little_project.egg-info/SOURCES.txt'
writing manifest file 'Our_little_project.egg-info/SOURCES.txt'
..
----------------------------------------------------------------------
Ran 2 tests in 0.006s

OK

C/C++扩展

前面的章节已经介绍了这一点,因为这是编译 C/C++文件的要求。但是那一章没有解释setup.py在这个案例中做了什么以及如何做。

为方便起见,我们将重复setup.py文件:

import setuptools

spam = setuptools.Extension('spam', sources=['spam.c'])

setuptools.setup(
    name='Spam',
    version='1.0',
    ext_modules=[spam],
)

在开始使用这些扩展之前,您应该学习以下命令:

  • build:这实际上不是一个 C/C++特定的构建函数(请尝试使用build_clib实现),而是一个组合构建函数,用于构建setup.py中的所有内容。
  • clean:清除build命令的结果。这通常是不需要的,但有时需要重新编译才能工作的文件的检测是不正确的。因此,如果遇到奇怪或意外的问题,请首先尝试清理项目。

定期扩展

setuptools.Extension类告诉setuptools名为spam的模块使用源文件spam.c。这只是扩展名、名称和源列表的最简单版本,但在许多情况下,您需要比简单的情况更多。

一个例子是pillow库,它检测系统上可用的库,并在此基础上添加扩展。但由于这些扩展包含库,因此需要一些额外的编译标志。基本 PIL 模块本身似乎不太复杂,但 LIB 实际上充满了所有自动检测的库,其中包含匹配的宏定义:

exts = [(Extension("PIL._imaging", files, libraries=libs,
                   define_macros=defs))]

freetype扩展具有类似的功能:

if feature.freetype:
    exts.append(Extension(
        "PIL._imagingft", ["_imagingft.c"], libraries=["freetype"]))

Cython 扩展

当涉及到扩展时,setuptools库实际上比常规distutils库要聪明一些。它实际上为Extension类增加了一个小技巧。还记得在第 12 章中对Cython的简要介绍吗?性能–跟踪并减少内存和 CPU 使用的性能?setuptools库使的编译更加方便。Cython手册建议您使用与以下代码类似的代码:

from distutils.core import setup
from Cython.Build import cythonize

setup(
    ext_modules = cythonize("eggs.pyx")
)

此处eggs.pyx包含:

def make_eggs(int n):
    print('Making %d eggs: %s' % (n, n * 'eggs '))

这种方法的问题是,除非您安装了Cython,否则setup.py将损坏:

# python setup.py build
Traceback (most recent call last):
 File "setup.py", line 2, in <module>
 import Cython
ImportError: No module named 'Cython'

为了防止这个问题,我们将让setuptools处理这个问题:

import setuptools

eggs = setuptools.Extension('eggs', sources=['eggs.pyx'])

setuptools.setup(
    name='Eggs',
    version='1.0',
    ext_modules=[eggs],
    setup_requires=['Cython'],
)

现在Cython将根据需要自动安装,代码将正常工作:

# python setup.py build
running build
running build_ext
cythoning eggs.pyx to eggs.c
building 'eggs' extension
...
# python setup.py develop
running develop
running egg_info
creating Eggs.egg-info
writing dependency_links to Eggs.egg-info/dependency_links.txt
writing top-level names to Eggs.egg-info/top_level.txt
writing Eggs.egg-info/PKG-INFO
writing manifest file 'Eggs.egg-info/SOURCES.txt'
reading manifest file 'Eggs.egg-info/SOURCES.txt'
writing manifest file 'Eggs.egg-info/SOURCES.txt'
running build_ext
skipping 'eggs.c' Cython extension (up-to-date)
copying build/... ->
Creating Eggs.egg-link (link to .)
Adding Eggs 1.0 to easy-install.pth file

Installed Eggs
Processing dependencies for Eggs==1.0
Finished processing dependencies for Eggs==1.0
# python -c 'import eggs; eggs.make_eggs(3)'
Making 3 eggs: eggs eggs eggs

然而,出于开发目的,Cython也提供了一种更简单的方法,不需要人工建造。首先,为了确保我们实际使用了这种方法,让我们安装Cython并完全卸载和清理eggs

# pip uninstall eggs -y
Uninstalling Eggs-1.0:
 Successfully uninstalled Eggs-1.0
# pip uninstall eggs -y
Cannot uninstall requirement eggs, not installed
# python setup.py clean
# pip install cython

现在让我们试着运行我们的eggs.pyx模块:

>>> import pyximport
>>> pyximport.install()
(None, <pyximport.pyximport.PyxImporter object at 0x...>)
>>> import eggs
>>> eggs.make_eggs(3)
Making 3 eggs: eggs eggs eggs

这就是在没有显式编译的情况下运行pyx文件是多么容易。

车轮——新鸡蛋

对于纯 Python 包,sdist(源代码分发)命令总是足够了。然而,对于 C/C++软件包来说,通常并不那么方便。C/C++软件包的问题是,除非使用二进制软件包,否则需要编译。传统上,这些文件通常是和.egg文件,但它们从未真正正确地解决问题。这就是为什么引入了wheel格式(PEP 0427),这是一种二进制包格式,包含源代码和二进制文件,可以在 Windows 和 OS X 上安装,而无需编译器。另外,对于纯 Python 软件包,它的安装速度也更快。

幸运的是,实现非常简单。首先,安装wheel包:

# pip install wheel

现在您可以使用bdist_wheel命令来构建包了。唯一一个小问题是,默认情况下,Python 3 创建的包只能在 Python 3 上工作,因此 Python 2 安装将退回到sdist文件。要解决此问题,您可以将以下内容添加到您的setup.cfg文件中:

[bdist_wheel]
universal = 1

这里唯一需要注意的是,在 C 扩展的情况下,这可能会出错。Python3 的二进制 C 扩展与 Python2 的二进制 C 扩展不兼容。因此,如果您有一个纯 Python 包,并且目标是 Python2 和 Python3,请启用该标志。否则,将其保留为默认值。

分发到 Python 包索引

一旦一切就绪并运行、测试和记录,就可以将项目实际推送到Python 包索引PyPI)。在将包裹推送到 PyPI 之前,我们需要确保一切正常。

首先,让我们检查setup.py文件中的问题:

# python setup.py check
running check
warning: check: missing required meta-data: url

warning: check: missing meta-data: either (author and author_email) or (maintainer and maintainer_email) must be supplied

似乎我们忘了指定urlauthormaintainer信息。让我们填写以下内容:

import setuptools

eggs = setuptools.Extension('eggs', sources=['eggs.pyx'])

setuptools.setup(
    name='Eggs',
    version='1.0',
    ext_modules=[eggs],
    setup_requires=['Cython'],
    url='https://wol.ph/',
    author='Rick van Hattem (Wolph)',
    author_email='wolph@wol.ph',
)

现在让我们再次检查:

# python setup.py check
running check

完美的没有错误,一切看起来都很好。

现在我们的setup.py已经就绪,让我们尝试测试。因为我们的小测试项目实际上没有测试,所以它几乎是空的。但是如果您正在启动一个新项目,那么我建议您从一开始就尝试保持 100%的测试覆盖率。稍后实现所有测试通常更困难,而在工作时进行测试通常会让您更多地考虑代码的设计决策。运行测试非常简单:

# python setup.py test
running test
running egg_info
writing dependency_links to Eggs.egg-info/dependency_links.txt
writing Eggs.egg-info/PKG-INFO
writing top-level names to Eggs.egg-info/top_level.txt
reading manifest file 'Eggs.egg-info/SOURCES.txt'
writing manifest file 'Eggs.egg-info/SOURCES.txt'
running build_ext
skipping 'eggs.c' Cython extension (up-to-date)
copying build/... ->

---------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

现在我们已经检查了所有内容,下一步是构建文档。如前所述,sphinxsphinx-pypi-upload-2包可以帮助您:

# python setup.py build_sphinx
running build_sphinx
Running Sphinx v1.3.5
...

一旦我们确定一切都是正确的,我们就可以构建包并将其上传到 PyPI。对于纯 Python 版本,可以使用sdist(源代码分发)命令。对于使用本机安装程序的软件包,有几个选项可用,例如bdist_wininstbdist_rpm。我个人几乎在我的所有软件包中都使用以下内容:

# python setup.py build_sphinx upload_sphinx sdist bdist_wheel upload

这将自动构建 Sphinx 文档,将文档上载到 PyPI,使用源代码构建包,并使用源代码上载包。

显然,只有当您是该特定包的所有者并获得 PyPI 的授权时,这才会成功。

在上传包之前,需要在 PyPI 上注册包。这可以使用register命令来完成,但由于该命令会立即在 PyPI 服务器上注册包,因此在测试时不应使用它。

总结

阅读本章之后,您应该能够创建 Python 包,其中不仅包含纯 Python 文件,还包含额外的数据、编译的 C/C++扩展、文档和测试。有了所有这些工具,您现在可以制作高质量的 Python 包,这些包可以轻松地在其他项目和包中重用。

Python 基础设施使创建新包和将项目拆分为多个子项目变得非常容易。这使您可以创建简单且可重用的包,但 bug 较少,因为所有内容都易于测试。虽然您不应该过分地拆分包,但是如果脚本或模块有自己的用途,那么它是单独打包的候选者。

有了这一章,我们就到了书的结尾。我真诚地希望你喜欢阅读它,并了解了新的和有趣的话题。非常感谢您的任何反馈,请随时通过我的网站与我联系 https://wol.ph/