Python 库打包

Python 有非常丰富的第三方库可以使用,很多开发者会向 pypi 上提交自己的 Python 包。要想向 pypi 包仓库提交自己开发的包,首先要将自己的代码打包,才能上传分发。Python 打包一般用到的库是 setuptools,下面我们重点来介绍 setuptools 的配置和使用。

安装

1
pip install setuptools

setup.py 文件

Python 库打包分发的关键在于编写 setup.py 文件。setup.py 文件编写的规则是从 setuptools 模块导入 setup 函数,并传入各类参数进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding:utf-8

from setuptools import setup

setup(
name='demo', # 包名字
version='1.0', # 包版本
description='This is a test of the setup', # 简单描述
author='huoty', # 作者
author_email='sudohuoty@163.com', # 作者邮箱
url='https://www.konghy.com', # 包的主页
packages=['demo'], # 包
)

参数概述

setup() 函数参数中,一部分是用来描述包的元信息;一部分用来定义了包运行的设置信息:

元信息

参数 说明
name 包名称
version 包版本
author 程序的作者
author_email 程序的作者的邮箱地址
maintainer 维护者
maintainer_email 维护者的邮箱地址
url 程序的官网地址
license 程序的授权信息
description 程序的简单描述
long_description 程序的详细描述
platforms 程序适用的软件平台列表
classifiers 程序的所属分类列表
keywords 程序的关键字列表
download_url 程序的下载地址

设置信息

参数 说明
packages 需要处理的包目录 (通常为包含 __init__.py 的文件夹)
py_modules 需要打包的 Python 单文件列表
cmdclass 添加自定义命令
package_data 指定包内需要包含的数据文件
include_package_data 自动包含包内所有受版本控制 (cvs/svn/git) 的数据文件
exclude_package_data include_package_dataTrue 时该选项用于排除部分文件
data_files 打包时需要打包的数据文件,如图片,配置文件等
ext_modules 指定扩展模块
scripts 指定可执行脚本,安装时脚本会被安装到系统 PATH 路径下
package_dir 指定哪些目录下的文件被映射到哪个源码包
requires 指定依赖的其他包
provides 指定可以为哪些模块提供依赖
install_requires 安装时需要安装的依赖包
entry_points 动态发现服务和插件,下面详细讲
setup_requires 指定运行 setup.py 文件本身所依赖的包
dependency_links 指定依赖包的下载地址
extras_require 当前包的高级 / 额外特性需要依赖的分发包
zip_safe 不压缩包,而是以目录的形式安装

参数详解

nameversion 等定义包的元信息,这些没有什么好说的,下面我们介绍几个与代码执行相关的几个参数:

packagesfind_packages()

定义了需要处理的包的目录,这通常为包含 __init__.py 的文件夹。对于简单工程来说,手动增加 packages 参数是容易。而对于复杂的工程来说,可能添加很多的包,这是手动添加就变得麻烦。setuptools 模块提供了一个 find_packages() 函数,它默认在与 setup.py 文件同一目录下搜索各个含有 __init__.py 的目录做为要添加的包。

1
find_packages(where='.', exclude=(), include=('*',))

find_packages 函数的第一个参数用于指定在哪个目录下搜索包,参数 exclude 用于指定排除哪些包,参数 include 指出要包含的包。

默认默认情况下 setup.py 文件只在其所在的目录下搜索包。如果不用 find_packages,想要找到其他目录下的包,也可以设置 package_dir 参数,其指定哪些目录下的文件被映射到哪个源码包,如: package_dir={'': 'src'} 表示 “root package” 中的模块都在 src 目录中。

包含数据文件

package_data
该参数是一个从包名称到 glob 模式列表的字典。如果数据文件包含在包的子目录中,则 glob 可以包括子目录名称。其格式一般为 {'package_name': ['files']},比如:package_data={'mypkg': ['data/*.dat'],}

include_package_data
该参数被设置为 True 时自动添加包中受版本控制的数据文件,可替代 package_data,同时,exclude_package_data 可以排除某些文件。注意当需要加入没有被版本控制的文件时,还是仍然需要使用 package_data 参数才行。

data_files
该参数通常用于包含不在包内的数据文件,即包的外部文件,如:配置文件,消息目录,数据文件。其指定了一系列二元组,即 (目的安装目录,源文件) ,表示哪些文件被安装到哪些目录中。如果目录名是相对路径,则相对于安装前缀进行解释。

manifest template:
manifest template 即编写 MANIFEST.in 文件,文件内容就是需要包含在分发包中的文件。一个 MANIFEST.in 文件如下:

1
2
3
include *.txt
recursive-include examples *.txt *.py
prune examples/sample?/build

MANIFEST.in 文件的编写规则可参考:https://docs.python.org/3.6/distutils/sourcedist.html

生成脚本

有两个参数 scripts 参数或 console_scripts 可用于生成脚本。

entry_points 参数用来支持自动生成脚本,其值是一个字典或者 *.ini 格式的字符串,从 entry_point 组名映射到一个表示 entry_point 的字符串或字符串列表,如:

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

scripts 参数是一个列表,安装包时在该参数中列出的文件会被安装到系统 PATH 路径下。如:

1
scripts=['bin/foo.sh', 'bar.py']

用如下方法可以将脚本重命名,例如去掉脚本文件的扩展名 (.py、.sh):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from setuptools.command.install_scripts import install_scripts

class InstallScripts(install_scripts):

def run(self):
setuptools.command.install_scripts.install_scripts.run(self)

# Rename some script files
for script in self.get_outputs():
if basename.endswith(".py") or basename.endswith(".sh"):
dest = script[:-3]
else:
continue
print("moving %s to %s" % (script, dest))
shutil.move(script, dest)

setup(
# other arguments here...
cmdclass={
"install_scripts": InstallScripts
}
)

其中,cmdclass 参数表示自定制命令,后文详述。

ext_modules

ext_modules 参数用于构建 C 和 C++ 扩展扩展包。其是 Extension 实例的列表,每一个 Extension 实例描述了一个独立的扩展模块,扩展模块可以设置扩展包名,头文件、源文件、链接库及其路径、宏定义和编辑参数等。如:

1
2
3
4
5
6
7
8
9
setup(
# other arguments here...
ext_modules=[
Extension('foo',
glob(path.join(here, 'src', '*.c')),
libraries = [ 'rt' ],
include_dirs=[numpy.get_include()])
]
)

详细了解可参考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options

zip_safe

zip_safe 参数决定包是否作为一个 zip 压缩后的 egg 文件安装,还是作为一个以 .egg 结尾的目录安装。因为有些工具不支持 zip 压缩文件,而且压缩后的包也不方便调试,所以建议将其设为 False,即 zip_safe=False。

自定义命令

Setup.py 文件有很多内置的的命令,可以使用 python setup.py –help-commands 查看。如果想要定制自己需要的命令,可以添加 cmdclass 参数,其值为一个 dict。实现自定义命名需要继承 setuptools.Command 或者 distutils.core.Command 并重写 run 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from setuptools import setup, Command

class InstallCommand(Command):
description = "Installs the foo."
user_options = [
('foo=', None, 'Specify the foo to bar.'),
]
def initialize_options(self):
self.foo = None
def finalize_options(self):
assert self.foo in (None, 'myFoo', 'myFoo2'), 'Invalid foo!'
def run(self):
install_all_the_things()

setup(
...,
cmdclass={
'install': InstallCommand,
}
)

依赖关系
如果包依赖其他的包,可以指定 install_requires 参数,其值为一个 list,如:

1
2
3
4
install_requires=[
'requests>=1.0',
'flask>=1.0'
]

指定该参数后,在安装包时会自定从 pypi 仓库中下载指定的依赖包安装。

此外,还支持从指定链接下载依赖,即指定 dependency_links 参数,如:

1
2
3
4
dependency_links = [
"http://packages.example.com/snapshots/foo-1.0.tar.gz",
"http://example2.com/p/bar-1.0.tar.gz",
]

分类信息
classifiers 参数说明包的分类信息。所有支持的分类列表见:https://pypi.org/pypi?%3Aaction=list_classifiers

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
classifiers = [
# 发展时期,常见的如下
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
'Development Status :: 3 - Alpha',

# 开发的目标用户
'Intended Audience :: Developers',

# 属于什么类型
'Topic :: Software Development :: Build Tools',

# 许可证信息
'License :: OSI Approved :: MIT License',

# 目标 Python 版本
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
]

setup.cfg 文件

1
2


附注

可以看到,setup.py 的编写是非常麻烦的,kennethreitz 大神专门为此开了个 Github: A Human’s Ultimate Guide to setup.py,可以以此为模板,编写自己的 setup.py 文件。

参考资料: