使用setuptools对Python进行打包分发

1. 分发工具 setuptools

由于 distutils 无法定义包之间的依赖关系,Python 研究人员开发了增强版的打包工具 setuptools 以便更好的创建和分发 python 包。setuptools 通过添加基本的依赖系统和相关功能,提供了自动包查询程序,用来自动获取包之间的依赖关系,并完成这些包的安装,大大降低了安装各种包的难度。

通常安装 python 时会自带 setuptools,如果没有可以使用 pip 安装。setuptools 简单易用,只需写一个简短的 setup.py 安装文件,就可以将开发完成的 Python 应用打包。

$ pip install setuptools

2. 使用安装包创建 wheel 文件

在当前目录下新建文件 setup.py,然后创建包 hello 模拟需要打包分发的源码包

.
+-- hello
|   +-- hello_world.py
|   +-- __init__.py
+-- setup.py
.
├── hello
│   ├── hello_world.py    # hello function 
│   └── __init__.py
└── setup.py

一个最简单的 setup.py 文件内容如下:

from setuptools import setup

setup(
    name = 'firstPKG',
    version = '0.01',
    packages=['hello'],
)

编辑 hello_world.py 文件

def hello():
    print('Hello world, welcome to setuptools!')

wheel 是官方现在推荐的打包方式,首先安装 wheel

$ pip install wheel

然后使用 bdist_wheel 打包

$ python setup.py bdist_wheel

执行成功后,目录下除了 dist 和 *.egg-info 目录外,还有一个 build 目录用于存储打包中间数据。dist 文件夹中 wheel 包的名称如 firstPKG-0.0.1-py3-none-any.whl,其中 py3 指明只支持 Python3。

可以使用参数 --universal,包名如 firstPKG-0.0.1-py2.py3-none-any.whl,表明 wheel 包同时支持 Python2 和 Python3。使用 universal 也成为通用 wheel 包,反之称为纯 wheel 包。

$ python setup.py bdist_wheel --universal

最终的目录树如下:

.
├── build
│   ├── bdist.linux-x86_64
│   └── lib
│       └── my_pkgs
│           ├── greet.py
│           └── __init__.py
├── dist
│   └── firstPKG-0.0.1-py2.py3-none-any.whl
├── firstPKG.egg-info
│   ├── dependency_links.txt
│   ├── PKG-INFO
│   ├── SOURCES.txt
│   └── top_level.txt
├── my_pkgs
│   ├── greet.py
│   ├── __init__.py
│   └── __pycache__
│       ├── greet.cpython-36.pyc
│       └── __init__.cpython-36.pyc
└── setup.py

3. 安装 wheel 文件

创建 wheel 文件之后,使用 pip 安装到本地 Python 的 site-packages 目录。

$ cd dist
$ pip install firstPKG-0.0.1-py2.py3-none-any.whl

进入 python 交互界面,就可以使用安装好的包

>>> import my_pkgs
>>> from my_pkgs import greet
>>> from my_pkgs.greet import hello
>>> hello
<function hello at 0x7fe2cd92b598>
>>> hello()
Hello, welcome to setuptools!

应用开发过程中会频繁变更,每次安装都需要先卸载旧版本很麻烦。如果使用 develop 开发模式安装,实际代码不会拷贝到 site-packages 目录下,而是在 site-packages 目录下生成一个指向当前应用的链接 firstPKG.egg-link,使得当前位置的源码改动能够立刻反映到 site-packages 中。

$ python setup.py develop

卸载安装好的包

$ pip uninstall firstPKG

4. 将 wheel 文件上传到 PyPI

wheel 包可以自己使用和传输给其他人使用,但是维护更新不方便,而 PyPI 作为 Python 的软件仓库,可以让所有人方便的上传和下载,以及管理三方库。

首先登录 PyPI官网,注册账号。虽然 setuptools 支持使用 setup.py upload 上传包文件到 PyPI,但只支持 HTTP 而被新的 twine 取代。

$ pip install twine
$ twine upload dist/*

输入 username 和 password 即上传至 PyPI。如果不想每次输入账号密码,可以在当前目录下创建 .pypirc 文件,内容如下:

[distutils]
index-servers =
    pypi
    pypitest

[pypi]
username: 
password: 

[pypitest]
repository: https://test.pypi.org/legacy/
username: 
password:

填上自己的账号密码即可,这里配置了官方的 pypi 和 pypitest,若要配置其他仓库,按格式添加。上传成功之后即回到 PyPI官网 用户主页即可看到上传的 firstPKG-0.0.1文件。

PyPI 主页显示会有延迟,所以不能马上搜索到结果,pip search 也可能 搜索不到,但已经可以使用 pip 安装。

5. setup() 参数详细解释

5.1 完整的 setup.py 示例

from setuptools import setup
from setuptools import find_packages

with open('README.rst', 'r', encoding='utf-8') as rd:
    long_description = rd.read()

setup(
    # 在 PyPI 上搜索的项目名称。
    name="HelloWorld", 
    # 项目版本号,一般由三部分组成:MAJOR, MINOR, MAINTENANCE
    version="0.0.1",
    # 列出项目内需要被打包的所有 package。一般使用 setuptools.find_packages() 自动发现
    # exclude 用于排除不打包的 package
    packages=find_packages(exclude=['contrib', 'docs', 'tests*']),

    # Project uses reStructuredText, so ensure that the docutils get
    # installed or upgraded on the target machine
    # 项目依赖的 Python 库,使用 pip 安装本项目时会自动检查和安装依赖。
    install_requires=['docutils>=0.3'],
    # 指定项目依赖的 Python 版本
    python_requires='>=3',
    # 项目依赖数据文件,数据文件必须放在项目目录内且使用相对路径。
    # 如果不指定作为目录的键为空串,则代表对所有模块操作
    package_data={
        # If any package contains *.txt or *.rst files, include them:
        '': ['*.txt', '*.rst'],
        # And include any *.msg files found in the 'hello' package, too:
        'hello': ['*.msg'],
    },
    # 如果数据文件存在于项目外,则可以使用 data_files 参数或者 MANIFEST.in 文件进行管理。
    # 如果用于源码包,则使用 MANIFEST.in;如果用于 wheel,则使用 data_files。
    # 上述设置将在打包 wheel 时,将 data/conf.yml 文件添加至 mydata 目录。
    data_files=[(‘mydata’, [‘data/conf.yml’])],

    # metadata for upload to PyPI
    # 作者信息
    author="example",
    author_email="example@example.com",
    # 项目的简短描述,一般一句话就好,会显示在 PyPI 上名字下端。
    description="This is an Example Package",
    # 对项目的完整描述,使用 long_description。如果此字符串是 rst 格式的,PyPI 会自动渲染成 HTML 显示。
    long_description = long_description,
    # 项目许可证
    license="GNU GPLv3",
    # 项目关键词列表
    keywords="hello world example examples",
    # project home page 通常为 GitHub上 的链接或者 readthedocs 的链接。
    url="http://example.com/HelloWorld/", 
    # 项目相关额外连接,如代码仓库,文档地址等
    project_urls={
        "Bug Tracker": "https://bugs.example.com/HelloWorld/",
        "Documentation": "https://docs.example.com/HelloWorld/",
        "Source Code": "https://code.example.com/HelloWorld/",
    }
)

5.2 其他初始化文件

在阅读 Github 上的 Python 库时,除了最基本核心的 setup.py 文件和主程序之外,还会看到其他一些文件。本节将介绍它们的作用和使用方法。

(1) setup.cfg
包含了构建时候的一些默认参数,例如在使用 bdist_wheel 的时候设置默认的 —universal 参数。

[bdist_wheel]

universal = 1

(2) README.rst/README.md
项目说明文档,使用 reStrutruedText 可以在 PyPI 上很好的渲染,但 Markdown 则支持不够好。

(3) MANIFEST.in
决定 setuptools 打包源码时还需要额外打包哪些文件。

# Include the README
include *.md

# Include the license file
include LICENSE.txt

# Include the data files
recursive-include data *

(4) LICENSE.txt
项目许可说明文件

setuptools 默认打包的文件包括 README.rst/README.mdsetup.cfgMANIFEST.in,所有其他文件,如 LICENSE.txt, 使用源码包时需要在 MANIFEST.in 里添加 include,使用 wheel 包时需要在 setup.cfg 添加

[metadata]
license_file = LICENSE.txt

PyPI 上传推荐配置

.
├── 项目文件夹
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── setup.cfg
└── setup.py
    -- name
    -- version
    -- author
    -- author_email
    -- url
    -- packages
    -- description
    -- package_data/data_files
-------------本文结束 感谢阅读-------------
0%