跳转至

How to build a Python Package

这里记录下如何将自己写的Python程序打包成库,并安装, 参考官方文档Packaging Python Projects

方法一:官方安装流程

首先,我们要有一个文件夹,比如名为mypkg的文件夹。

之后我们需要将我们主要的源文件,比如myml文件夹(比如里面是一些机器学习的算法实现), 添加到mypkg中。

之后我们需要两个辅助文件:setup.pyREADME.md

其中README.md写上项目简短的说明即可。

setup.py需要按照一定的格式写,比如下面这样:

import setuptools

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="example-pkg-your-username",
    version="0.0.1",
    author="Example Author",
    author_email="author@example.com",
    description="A small example package",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://github.com/pypa/sampleproject",
    packages=setuptools.find_packages(),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
)

之后呢,我们就可以将上面的文件夹打包并发布了,发布之后便可以自行通过pip安装。具体可以参考上面官网的链接。

但是,我们这里可能会有一个需求,那就是,我们可能要持续开发这个库。这样的话,我们可以用pip提供的“Editable” Installs, 如此可以进入“Development Mode”。也即,我们只需要在mypkg上层文件夹下运行pip install -e mypkg就可以直接进入开发模式。这时候我们已经可以在终端(但是记得不要在myml文件下,不然就会无法调用)运行Python, 并直接通过import myml来完成调用。

这里我们对代码进行更改后,只需要在mypkg文件下用命令行运行python setup.py build就可以将新的改动提交上去。最后在我们将项目开发完时,运行python setup.py develop --uninstall将开发版本卸载掉,之后重新打包发布,在安装正式版本即可。

__init__.py文件的写法

首先明确__init__.py文件存在,则其所在的文件夹被自动视为一个package,在运行import package_name之后,其对应的__init__.py首先被调用。

按照文档,最顶层的__init__.py可以只写上库名字:name = "example_pkg"

其他的一般空下,不写任何东西,只是作为一个标识。

也有时候会用于多个submodules的一起调用或者限制调用,具体可以参考Python Cookbook3-Chap10.

测试

在写完需求后,写简单的单元测试来验证API的正确性,同时也避免了通过打印测试的局限性。目前主要用的还是unittest库,结合vscode使用非常方便。

文档

对于一个完整的库来说,文档是必不可少的,这里选用的是用Sphinx来做项目的文档。大致流程可以先参考下这里, 将文档通过Github发布,可以参考Open Source Options的这个教程, 最后还有些细枝末节的问题,可以看这篇文档

上面的教程总体感觉都挺乱的,这里进行下汇总。

Step1: 本地生成

首先安装sphinx: pip install sphinx.

之后在mypkg新建文件夹sphinx, 之后进入该文件夹cd sphinx,并运行sphinx-quickstart, 根据提示输入对应的信息。(这里填错了后面还可以改的)

这时候,可以根据reStructuredText 语法写改sphinx文件夹下的.rst文件,指定生成文档的内容与格式等。

之后修改sphinx/source/conf.py文件设置autodoc,参考这里。因为我在设置的时候没有遇到让加入extensions的提示,所以就手动设置,修改结果如下:

extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.intersphinx",
    "sphinx.ext.todo",
    "sphinx.ext.viewcode",
    "sphinx_rtd_theme",
]

这里还有去掉下面路径设置的注释

import os
import sys
sys.path.insert(0, os.path.abspath('.'))

然后保存(其他设置可以随便找个开源项目看看别人怎么写的,比如requests库),在sphinx文件夹下,终端执行sphinx-apidoc -o ./source ../src/

最后是生成html文件,make html

此时可以打开sphinx/build/html/index.html打开文档。

Step2: 发布文档

发布的方法这篇文档写的比较详细,可以照着作下,我这里并没有测试,而是按照Open Source Options最后一集比较粗暴的方法。

就是在mypkg文件夹下新建一个docs文件夹,之后将html文件夹下的文档全部复制过去,再新建一个.nojekyll文件,之后直接commit, push到github。最后在项目的setting中选择githubpages的设置,选中docs文件夹即可。

之后重新生成文档,同样将html文件夹下的文件全部复制到docs再提交就是了。注意这里.nojekyll文件十分重要,如果没有这个文件(而是选择某个主题),会出现文档页面无法显示的情况。

在全部完成之后就可以打包发布了。其他可能有些设置没有提到,可以看下我的ToyData项目的设置。

方法二:自动化构建

之前的项目都是按照上面的方法一来构建和发布的,但是总体来看,流程还是有一些复杂的...(当然了,全面地了解下这个流程还是很有必要的)

所以就查了下一些自动化构建的工具,在测试过之后采取了Poetry+MkDocs的组合: Poetry用于库文件的创建,库的本地打包与PyPI上传; MkDocs用于文档的生成与部署。下面简单介绍下整个库从重建到发布的流程。(Poetry和MkDocs的安装见官网指引)

本地创建库

命令行执行poetry new poetry-mkdocs-shenxzh, 输出Created package poetry_mkdocs_shenxzh in poetry-mkdocs-shenxzh.

查看文件目录

➜ tree 
.
├── poetry_mkdocs_shenxzh
│   └── __init__.py
├── pyproject.toml
├── README.rst
└── tests
    ├── __init__.py
    └── test_poetry_mkdocs_shenxzh.py

我们在poetry_mkdocs_shenxzh文件夹下放我们的库源码,在tests文件夹下做测试。为了符合一般习惯,可以把README.rst改成README.md, pyproject.toml是项目的主要配置文件,包括库依赖等设置。

后面就要进行库的开发了,我们用Git记录开发过程,故在pyproject.toml同目录(后称之为根目录)下运行git init初始化项目。

之后添加库文件,修改README.md文件, .gitignore,测试...

之后在根目录下执行mkdocs new docs, 此时docs文件夹目录

➜ tree .
.
├── docs
│   └── index.md
└── mkdocs.yml

我们将在docs/docs文件夹下放我们的文档源文件,这里是.md格式的。mkdocs.yml是MkDocs的配置文件。

我们在docs/docs文件夹下加入intro.md之后修改mkdocs.yml

site_name: My Docs
nav:
    - Home: index.md
    - Introduction: intro.md

之后mkdocs serve就可以在打开本地服务,查看文档界面。调整合适之后即可mkdocs build生成html文件,位于./docs/site文件夹下。因为我们一般不把site目录加入Git(只用docs/docs下面的md文件就可以很好地追踪文档变化),所以将docs/site加入.gitignore之后再add, commit。(如果已经commit也可以用git rm -r --cached docs/site将其删除)

现在我们已经完成了库的开发和文档的本地生成。下面首先将项目发布到GitHub,然后发布文档,最后将整个库打包发布。

项目发布到GitHub

首先在Github新建项目poetry-mkdocs-shenxzh,之后复制仓库地址,执行git remote add origin https://github.com/shenxiangzhuang/poetry-mkdocs-shenxzh.git加入远程仓库地址。

发布文档

./docs文件夹下执行mkdocs gh-deploy,输出

➜ mkdocs gh-deploy
INFO    -  Cleaning site directory 
INFO    -  Building documentation to directory: /home/shensir/Documents/CS/MyPrograming/Python/poetry-mkdocs-shenxzh/docs/site 
INFO    -  Documentation built in 0.23 seconds 
WARNING -  Version check skipped: No version specified in previous deployment. 
INFO    -  Copying '/home/shensir/Documents/CS/MyPrograming/Python/poetry-mkdocs-shenxzh/docs/site' to 'gh-pages' branch and pushing to GitHub. 
INFO    -  Your documentation should shortly be available at: https://shenxiangzhuang.github.io/poetry-mkdocs-shenxzh/ 

这里直接访问https://shenxiangzhuang.github.io/poetry-mkdocs-shenxzh/即可查看文档了。(我们可以在https://github.com/shenxiangzhuang/poetry-mkdocs-shenxzh/settings 中看到,GitHub Page的站点建立在gh-pages分支(mkdocs gh-deploy帮我们自动创建的)的根目录下。

发布库到PyPI

这里就是Poetry发挥作用的时候了,根目录执行poetry build把轮子造好(会自动创建./dist文件夹,然后将build好的文件放在这里)

➜ poetry build     
Building poetry-mkdocs-shenxzh (0.1.0)
  - Building sdist
  - Built poetry-mkdocs-shenxzh-0.1.0.tar.gz
  - Building wheel
  - Built poetry_mkdocs_shenxzh-0.1.0-py3-none-any.whl

之后执行poetry publish即可直接将库发布。

➜ poetry publish

Username: IronSky
Password: 
Publishing poetry-mkdocs-shenxzh (0.1.0) to PyPI
 - Uploading poetry-mkdocs-shenxzh-0.1.0.tar.gz 100%
 - Uploading poetry_mkdocs_shenxzh-0.1.0-py3-none-any.whl 100%

此时登录https://pypi.org/manage/projects/即可看到发布的库,这时执行pip install poetry-mkdocs-shenxzh就可以安装了。因为我本地已经把默认源头换成了豆瓣源,所以需要pip install poetry-mkdocs-shenxzh -i https://pypi.org/simple才可以

➜ pip install poetry-mkdocs-shenxzh -i https://pypi.org/simple
Collecting poetry-mkdocs-shenxzh
  Downloading poetry_mkdocs_shenxzh-0.1.0-py3-none-any.whl (1.4 kB)
Installing collected packages: poetry-mkdocs-shenxzh
Successfully installed poetry-mkdocs-shenxzh-0.1.0

之后我们就可以使用我们自己写的库了!

In [1]: from poetry_mkdocs_shenxzh.hello import Hello

In [2]: h = Hello()

In [3]: h.sayHello()
Hello World!

后记

用MkDocs替换Sphinx其实有个问题,那就是没办法自动化生成API Doc,就是Python的Docstring不能直接生成文档。(暂时没找到好的插件可以做到这一点)

所以用MkDocs在不需要对源码做大量解释的情况下还是很方便的,如果要对源码做更多的介绍,可能还是Sphinx好一些?


最后更新: 2023年1月29日