到目前为止,本章已经介绍了如何编写、测试和调试 Python 代码。所有这些,只剩下一件事,那就是打包和分发 Python 库/和应用。为了创建可安装的软件包,我们将使用setuptools软件包,该软件包最近与 Python 捆绑在一起。如果您以前创建过包,您可能还记得distribute和distutils2,但非常重要的是要记住,它们都已被setuptools和distutils所取代,您不应该再使用它们了!
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目录中安装一个指向包实际所在路径的链接。这允许您修改代码并立即在运行的脚本和应用中看到结果,而无需在每次更改后重新安装代码。
与常规安装一样,pip和setup.py版本均可用:
pip install –e package_directory以及setup.py版本:
cd package_directory
python setup.py develop前几章实际上已经向我们展示了几个例子,但让我们重申并回顾一下最重要的部分实际上做了什么。您将在本章中使用的核心功能是setuptools.setup。
对于最简单的包,与 Python 捆绑的distutils包也将足够了,但我还是推荐setuptools。setuptools包具有distutils所缺乏的许多重要功能,几乎所有 Python 环境都将有setuptools可用。
在继续之前,请确保您拥有pip和setuptools的最新版本:
pip install -U pip setuptoolssetuptools和distutils包在过去几年发生了重大变化,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/',
)这是相当多的代码和注释,但它涵盖了现实生活中遇到的大多数选项。这里讨论的最有趣、最通用的参数将在以下各节中分别介绍。
其他文档可以在pip和setuptools文档以及 Python 打包用户指南中找到:
- http://pythonhosted.org/setuptools/
- https://pip.pypa.io/en/stable/
- http://python-packaging-user-guide.readthedocs.org/en/latest/
在我们的示例中,我们仅使用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命令可能非常有用。一个例子是我在所有包中使用的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 cmd –help
# 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.py和setup.cfg文件- 测试:
test/test*.py examples目录中的所有*.txt和*.py文件- 根目录中的所有
*.txt文件
在默认值之后,我们有了第一个解决方案:setup 函数的package_data参数。其语法非常简单,一个字典,其中键是包,值是要包含的模式:
package_data = {
'docs': ['*.rst'],
}第二种解决方案是使用MANIFEST.in文件。此文件包含要包括、排除等的模式。include和exclude命令使用模式进行匹配。这些模式是全局模式(参见glob模块获取文档:https://docs.python.org/3/library/glob.html )和包含和排除命令有三种变体:
include/exclude:这些命令只对给定的路径有效,没有其他作用recursive-include/recursive-exclude:这些命令与include/exclude命令类似,但递归处理给定路径global-include/global-exclude:对这些文件要非常小心,它们会在源代码树中的任何位置包含或排除这些文件
除了include/exclude命令外,还有另外两个命令;graft和prune命令,包括或排除包含给定目录下所有文件的目录。这对于测试和文档非常有用,因为它们可以包含非标准文件。除了这些示例之外,显式地包含所需的文件并忽略所有其他文件几乎总是更好的。下面是一个例子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命令,转而尝试nose或py.test。
py.test包有几种集成方法:pytest-runner、您自己的测试命令,以及不推荐的生成runtests.py脚本进行测试的方法。如果您的某个软件包仍在使用runtests.py,我强烈建议您切换到其他选项之一。
但是在我们讨论其他选项之前,让我们先确定我们有一些测试。因此,让我们在包中创建一个测试。我们将其存储在test_pytest.py中:
def test_a():
pass
def test_b():
pass现在,其他测试选项。由于自定义命令实际上并没有添加太多内容,实际上使事情变得更复杂,因此我们将跳过它。如果要自定义测试的运行方式,请使用pytest.ini和setup.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++文件的要求。但是那一章没有解释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"]))当涉及到扩展时,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 包索引(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似乎我们忘了指定url和author或maintainer信息。让我们填写以下内容:
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现在我们已经检查了所有内容,下一步是构建文档。如前所述,sphinx和sphinx-pypi-upload-2包可以帮助您:
# python setup.py build_sphinx
running build_sphinx
Running Sphinx v1.3.5
...一旦我们确定一切都是正确的,我们就可以构建包并将其上传到 PyPI。对于纯 Python 版本,可以使用sdist(源代码分发)命令。对于使用本机安装程序的软件包,有几个选项可用,例如bdist_wininst和bdist_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/ 。