How to write a Python package?

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

方法一:官方安装流程

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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的提示,所以就手动设置,修改结果如下:

1
2
3
4
5
6
7
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.viewcode",
"sphinx_rtd_theme",
]

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

1
2
3
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.

查看文件目录

1
2
3
4
5
6
7
8
9
➜ 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文件夹目录

1
2
3
4
5
➜ tree .
.
├── docs
│   └── index.md
└── mkdocs.yml

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

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

1
2
3
4
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,输出

1
2
3
4
5
6
7
➜ 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好的文件放在这里)

1
2
3
4
5
6
➜ 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即可直接将库发布。

1
2
3
4
5
6
7
➜ 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`才可以

1
2
3
4
5
➜ 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

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

1
2
3
4
5
6
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好一些?

本文标题:How to write a Python package?

文章作者:不秩稚童

发布时间:2019年07月13日 - 14:09:10

最后更新:2021年01月13日 - 17:25:45

原始链接:http://datahonor.com/2019/07/13/How-to-write-a-Python-package/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

击蒙御寇