1. 准备工作
1.1 必备python工具包
setuptools: setuptools是一组Python的 distutils工具的增强工具,可以让程序员更方便的创建和发布 Python 包,特别是那些对其它包具有依赖性的状况。
wheel: wheel库是PEP 427中定义的Python wheel打包标准的参考实现。"wheel"是python的一种内置包格式,它是一种zip格式的存档文件,具有特殊格式的文件名和.whl扩展名。
twine: twine是一个用于在PyPI上发布Python包的python库,它为新项目和现有项目提供了独立于构建系统的源文件和二进制发布工件的上传。
1.2 Notes
dist的含义:dist全称为distribute,意为分发、发布等意思,许多程序框架中的dist文件夹的用途是存放最终发行版的程序源码。

Python 包的分发方式可分为两种:①以源码包的方式发布:源码包的本质是一个压缩包(.zip/.tar.gz等),其安装的过程是先解压→再编译→最后安装,所以它是跨平台的,由于每次安装都要进行编译,相对二进包安装方式来说安装速度较慢。②以二进制包的方式发布:二进制包(.egg/.wheel)的安装过程省去了编译的过程,直接进行解压安装,所以安装速度较源码包来说更快,由于不同平台的编译出来的包无法通用,所以在发布时,需事先编译好多个平台的包。

Egg格式与Wheel格式的区别:Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 年定义。Wheel 的出现是为了替代 Egg,它的本质是一个zip包,现在被认为是 Python 的二进制包的标准格式。

2. 操作流程
2.1 创建setup.py文件
——setup.py是包分发与安装的指导文件。

首先,在待打包的库同级目录下创建一个setup.py文件,如下:

|--library
    |--xx.py
    |--xx.py
|--setup.py
|--README.md
然后,编写setup.py文件,如下:

# from distutils.core import setup
from setuptools import setup, find_packages

with open("README.md", 'r') as fh:
    long_description = fh.read()
    
setup(
    name="library name", # A string specifying the name of the package.
    version="0.0.1",  # A string specifying the version number of the package.
    author="author name",  # A string specifying the author of the package. 
    author_email="author@example.com",  # A string specifying the email address of the package author.
    # maintainer="maintainer name",  # A string specifying the name of the current maintainer
    # maintainer_email="maintainer@example.com", # A string specifying the email address of the current maintainer
    description="A small example library",  # A string describing the package in a single line.
    long_description=long_description,  # A string providing a longer description of the package.
    long_description_content_type="text/markdown",  # A string specifying the content type is used for the long_description
    url="https://github.com/xxx",  # A string specifying the URL for the package homepage.
    # download_url="xxx.xxx",  # A string specifying the URL to download the package.
    packages=find_packages(),  # A list of strings specifying the packages (dictories generally including __init__.py) that setuptools will manipulate.
    classifiers=[
        "Development Status :: 3 - Alpha",  # 3 - Alpha, 4 - Beta, 5 - Production/Stable
        
        "Topic :: Software Development :: Build Tools",
        
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.7",
        # "Programming Language :: Python :: 3.8",
        
        "License :: OSI Approved :: MIT License",
        
        "Operating System :: OS Independent",
    ],  # A list of strings describing the categories for the package.
    python_requires='>=3',  # A string corresponding to a version specifier for the Python version.
    py_modules=['xx/inference']  # A list of strings specifying the modules that setuptools will manipulate.
    # install_requires = ['argparse', 'cv2']  # A string or list of strings specifying what other distributions need to be installed when this one is. (即安装此库时,自动会安装install_requires指定的库)
)

更详细的参数说明见keywords。

PS: setuptools.find_packages函数说明:

# find_packages函数默认在与 setup.py 文件同一目录下搜索各个含有 __init__.py 的目录做为要添加的包。
find_packages(where='.', exclude=(), include=('*',))
# where指定在哪个目录下搜索包,exclude指定搜索要排除哪些包,include指出搜索要包含的包。
PS: 当然还有另一种编写setup.py文件的方式,即先编写setup.cfg文件,然后通过pbr等解析工具,详见此。

2.2 构建分发包
通过setup.py的一些内置命令,即可构建分发包:

python setup.py command1 command2 ... 
常用commands如下:

install: 在本地将包安装到系统环境中。
develop: 此命令不会真正安装包,而是在系统环境中创建一个软链接指向包实际所在目录,这样在修改包之后不用再安装就能生效,便于调试。
build: 构建安装时所需的所有内容。
sdist: 构建源码分发包,在Windows下为zip格式,在Linux下为tar.gz格式。执行 sdist 命令时,默认会被打包的文件:
所有 py_modules 或 packages 指定的源码文件
所有 ext_modules 指定的文件
所有 package_data 或 data_files 指定的文件
所有 scripts 指定的脚本文件
README、README.txt、setup.py 和 setup.cfg文件
bdist: 构建二进制分发包。
bdist_egg: 构建二进制 egg 分发包,经常用来替代基于 bdist 生成的模式。
bdist_wheel:构建二进制 wheel 分发包,whl 包是新的标准,用于替代过时的egg包。
bdist_wininst: 构建Windows中使用的exe二进制软件包,双击即可安装。
bdist_rpm: 构建linux中使用的rpm软件包。
一个构建源码分发包和二进制分发包的小例子,构建好的分发包会自动存储在dist文件夹下:

python setup.py sdist bdist_wheel
|--build
    |--xxx
|--dist
    |--{library}-{version}.tar.gz
    |--{library}-{version}-py{py_version}-none-any.whl
|--library
    |--xx.py
    |--xx.py
|--library.egg-info
    |--xxx
|--setup.py
|--README.md
2.3 发布(上传)包到PyPI
在构建好分发包后,就可以将分发包上传发布到PyPI中,这样就可以作为一个开源库通过pip install的方式安装使用了。

注意:在上传到PyPI之前,需要在PyPI上注册账号。在实际上传到PyPI前,也可以先尝试上传到TestPyPI做测试,TestPyPI是PyPI的一个单独实例,它允许在不影响实际索引的情况下测试分发工具和进程,TestPyPI一样需要注册账号。注册好后,可以配置一个. p y p i r c .pypirc.pypirc文件,此文件包含了PyPI访问地址和账号等信息,大致内容如下:

[distutils]
index-servers = 
    pypi
    testpypi

[pypi]
repository = https://pypi.org/
username:xxx
password:xxx

[testpypi]
repository = https://test.pypi.org/
username:xxx
password:xxx
方法一:

(1)首先注册项目信息,执行完下方命令之后即可在PyPI中看到项目信息。

python setup.py register
(2)上传源码包,之后才可通过pip install下载安装。

python setup.py upload
# python setup.py sdist bdist_wheel upload # 同时上传两种分发包

此方法不太推荐。安全性跟可测试性差,只有在构建系统、Python版本和底层操作系统配置都正确的情况下,才能正确且安全地工作。而且,此命令需在构建源码包等命令后最后一步调用,也就是说不能再上传之前测试包。

方法二:(推荐使用)

方法二是通过python的twine库,它是一个专门用于与PyPI交互的工具,其目标就是通过改进安全性和可测试性来改进PyPI交互,其余详细介绍见此。

具体使用(在构建分发包后):

上传到TestPyPI
twine upload -r testpypi dist/*
上传到PyPI
twine upload dist/*
通过简单的一行代码即可将自己的包发布上传到PyPI/TestPyPI中了(执行代码后,会要求操作者输入PyPI/TestPyPI的账户密码进行上传,twine也会自动在home目录下查找.pypirc文件获取账号信息,也可以通过–config-file选项指定文件路径)。

PS:twine check dist/* \text{twine check dist/*}twine check dist/*命令可以检查用户发行版的长描述是否能在PyPI上正确呈现。