打包和分发 Python 代码可能是一项复杂且有时令人困惑的任务,尤其是当您的项目有很多依赖项或涉及比直接 Python 代码更奇特的组件时。然而,在许多情况下,以标准方式让其他人可以访问您的代码是非常简单的,我们将在本节中使用标准的distutils模块来了解如何做到这一点。distutils的主要优点是它包含在 Python 标准库中。除了最简单的打包需求之外,您可能还想看看安装工具,它的功能超出了distutils的功能,但相应地,它更容易混淆。
distutils 模块允许您编写一个简单的 Python 脚本,它知道如何将 Python 模块安装到任何 Python 安装中,包括托管在虚拟环境中的 Python 安装。按照惯例,这个脚本称为setup.py,它存在于项目结构的顶层。然后可以执行此脚本以执行实际安装。
让我们看一个简单的distutils示例。我们将为我们在第 11 章、与 PDB调试中编写的回文模块创建一个基本的setup.py安装脚本。
我们要做的第一件事是创建一个目录来保存我们的项目。我们称之为palindrome:
$ mkdir palindrome
$ cd palindrome让我们把我们的palindrome.py副本放在这个目录中:
"""palindrome.py - Detect palindromic integers"""
import unittest
def digits(x):
"""Convert an integer into a list of digits.
Args:
x: The number whose digits we want.
Returns: A list of the digits, in order, of ``x``.
>>> digits(4586378)
[4, 5, 8, 6, 3, 7, 8]
"""
digs = []
while x != 0:
div, mod = divmod(x, 10)
digs.append(mod)
x = div
return digs
def is_palindrome(x):
"""Determine if an integer is a palindrome.
Args:
x: The number to check for palindromicity.
Returns: True if the digits of ``x`` are a palindrome,
False otherwise.
>>> is_palindrome(1234)
False
>>> is_palindrome(2468642)
True
"""
digs = digits(x)
for f, r in zip(digs, reversed(digs)):
if f != r:
return False
return True
class Tests(unittest.TestCase):
"Tests for the ``is_palindrome()`` function."
def test_negative(self):
"Check that it returns False correctly."
self.assertFalse(is_palindrome(1234))
def test_positive(self):
"Check that it returns True correctly."
self.assertTrue(is_palindrome(1234321))
def test_single_digit(self):
"Check that it works for single digit numbers."
for i in range(10):
self.assertTrue(is_palindrome(i))
if __name__ == '__main__':
unittest.main()最后让我们创建setup.py脚本:
from distutils.core import setup
setup(
name = 'palindrome',
version = '1.0',
py_modules = ['palindrome'],
# metadata
author = 'Austin Bingham',
author_email = 'austin@sixty-north.com',
description = 'A module for finding palindromic integers.',
license = 'Public domain',
keywords = 'palindrome',
)文件中的第一行从distutils.core模块导入我们需要的功能,即setup()函数。这个函数完成了安装代码的所有工作,所以我们需要告诉它我们正在安装的代码。当然,我们通过传递给函数的参数来实现这一点。
我们首先告诉setup()的是这个项目的名称。在本例中,我们选择了回文,但您可以选择任何您喜欢的名称。不过,一般来说,最简单的方法是将名称与项目名称保持一致。
我们传递给setup()的下一个参数是版本。同样,这可以是您想要的任何字符串。Python 不依赖该版本来遵循任何规则。
下一个论点py_modules可能是最有趣的。我们使用它来指定要安装的 Python 模块。此列表中的每个条目都是不带.py扩展名的模块名称。setup()功能将查找匹配的.py文件并安装它。因此,在我们的示例中,我们要求setup()安装palindrome.py,当然,这是我们项目中的一个文件。
我们在这里使用的其他论点都是不言自明的,主要是为了帮助人们正确使用您的模块,并知道如果他们有问题,应该联系谁。
在开始使用setup.py之前,我们首先需要创建一个虚拟环境,在其中安装模块。在回文目录中,创建一个名为palindrome_env的虚拟环境:
$ python3 -m venv palindrome_env完成后,激活新环境。在 Linux 或 macOS 上,源激活脚本:
$ source palindrome_env/bin/activate或在 Windows 上直接调用脚本:
> palindrome_env\bin\activate现在我们有了setup.py,我们可以用它做很多有趣的事情。首先,也许是最明显的,我们可以做的是将我们的模块安装到我们的虚拟环境中!我们通过将 install 参数传递给setup.py来实现这一点:
(palindrome_env)$ python setup.py install
running install
running build
running build_py
copying palindrome.py -> build/lib
running install_lib
copying build/lib/palindrome.py -> /Users/sixty_north/examples/palindrome/palindrome_env/lib/python3.5/site-packages
byte-compiling /Users/sixty_north/examples/palindrome/palindrome_env/lib/python3.5/site-packages/palindrome.py to palindrome.cpython-35.pyc
running install_egg_info
Writing /Users/sixty_north/examples/palindrome/palindrome_env/lib/python3.5/site-packages/palindrome-1.0-py3.5.egg-info调用时,setup()打印出几行,告诉您其进度。对我们来说,最重要的一行是将palindrome.py复制到安装文件夹的实际位置:
copying build/lib/palindrome.py -> /Users/sixty_north/examples/palindrome/palindrome_env/lib/python3.5/site-packagesPython 安装的site-packages目录通常是安装像我们这样的第三方软件包的地方。看来安装工作正常。
让我们通过运行 Python 并查看模块是否可以导入来验证这一点。请注意,我们希望在执行此操作之前更改目录,否则,当我们导入回文时,Python 只需在当前目录中加载源文件:
(palindrome_env)$ cd ..
(palindrome_env)$ python
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 26 2016, 10:47:25)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import palindrome
>>> palindrome.__file__
'/Users/sixty_north/examples/palindrome/palindrome_env/lib/python3.5/site-packages/palindrome.py'在这里,我们使用模块上的__file__属性来查看它是从何处导入的,我们看到它是从虚拟环境的site-packages导入的,这正是我们想要的。
退出 Python REPL 后,不要忘记切换回源目录:
(palindrome_env)$ cd palindromesetup()的另一个有用特性是,它可以创建各种类型的“分发”格式。它将把您指定的所有模块打包成易于分发给其他人的包。您可以使用sdist命令(这是“源代码分发”的缩写)来完成此操作:
(palindrome_env)$ python setup.py sdist --format zip
running sdist
running check
warning: check: missing required meta-data: url
warning: sdist: manifest template 'MANIFEST.in' does not exist (using default file list)
warning: sdist: standard file not found: should have one of README, README.txt
writing manifest file 'MANIFEST'
creating palindrome-1.0
making hard links in palindrome-1.0...
hard linking palindrome.py -> palindrome-1.0
hard linking setup.py -> palindrome-1.0
creating dist
creating 'dist/palindrome-1.0.zip' and adding 'palindrome-1.0' to it
adding 'palindrome-1.0/palindrome.py'
adding 'palindrome-1.0/PKG-INFO'
adding 'palindrome-1.0/setup.py'
removing 'palindrome-1.0' (and everything under it)如果我们看一下,我们将看到该命令创建了一个新目录dist,其中包含新生成的分发文件:
(palindrome_env) $ ls dist
palindrome-1.0.zip如果我们解压缩该文件,我们将看到它包含我们项目的源代码,包括setup.py:
(palindrome_env)$ cd dist
(palindrome_env)$ unzip palindrome-1.0.zip
Archive: palindrome-1.0.zip
inflating: palindrome-1.0/palindrome.py
inflating: palindrome-1.0/PKG-INFO
inflating: palindrome-1.0/setup.py所以现在你可以将这个 zip 文件发送给任何想使用你的代码的人,他们可以使用setup.py将它安装到他们的系统中。非常方便!
注意,sdist命令可以生成各种类型的分布。要查看可用选项,您可以使用--help-formats选项:
(palindrome_env) $ python setup.py sdist --help-formats
List of available source distribution formats:
--formats=bztar bzip2'ed tar-file
--formats=gztar gzip'ed tar-file
--formats=tar uncompressed tar file
--formats=zip ZIP file
--formats=ztar compressed tar file本节仅涉及distutils的基本内容。通过将--help传递给setup.py,您可以了解更多有关如何使用distutils的信息:
(palindrome_env) $ python setup.py --help
Common commands: (see '--help-commands' for more)
setup.py build will build the package underneath 'build/'
setup.py install will install the package
Global options:
--verbose (-v) run verbosely (default)
--quiet (-q) run quietly (turns verbosity off)
--dry-run (-n) don't actually do anything
--help (-h) show detailed help message
--command-packages list of packages that provide distutils commands
Information display options (just display information, ignore any commands)
--help-commands list all available commands
--name print package name
--version (-V) print package version
--fullname print <package name>-<version>
--author print the author's name
--author-email print the author's email address
--maintainer print the maintainer's name
--maintainer-email print the maintainer's email address
--contact print the maintainer's name if known, else the author's
--contact-email print the maintainer's email address if known, else the
author's
--url print the URL for this package
--license print the license of the package
--licence alias for --license
--description print the package description
--long-description print the long package description
--platforms print the list of platforms
--classifiers print the list of classifiers
--keywords print the list of keywords
--provides print the list of packages/modules provided
--requires print the list of packages/modules required
--obsoletes print the list of packages/modules made obsolete
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对于许多简单的项目,您会发现我们刚才介绍的内容几乎是您需要知道的全部。