# 0.基本介绍
Python
的强大之处就是它的丰富的开源包,如何开发一个自己的Python Package
并将其发布到Pypi
呢?在这里记录一下完整的过程。
# 1.__init__.py
文件
python 中的包分成两种,一种是普通包regular package
,一种是命名空间包namespace package
。
# 1.1 Regular Package
普通包是指包含一个 __init__.py
文件的普通目录,在Python 3.2
及之前的版本中一直使用的都是这种包。普通包在导入的时候,会隐式自动调用包中的__init__.py
文件,__init__.py
中定义的对象,变量都会被限制在包名的命名空间中。
如下形式的包:
parent/
__init__.py
one/
__init__.py
two/
__init__.py
three/
__init__.py
导入parent.one
包时,会自动执行parent/__init__.py
和parent/one/__init__.py
,导入其他的包时情况也类似。
总结,只需要记住,__init__.py
是包导入时最先执行的文件。
_init__.py
中还有一个重要的变量,__all__
, 它用来将模块全部导入,也就是支持使用from package import *
。
__all__ = ['os', 'sys', 're', 'urllib']
# 1.2 namespace package
命名空间包是指一种特殊的 Python 包,在Python 3.3
之后引入,它不包含__init__.py
文件,而是由一个或多个不同的目录组成,每个目录都可以包含一个或多个模块。
如有一个命名空间包namespace package
包nsp
,在目录/xx/test_cpp/mypkg/ns
和/xx/test_cpp/mypkg/pkg
下各有一个nsp
是文件夹,其中分别包含nsp1.py
和nsp2.py
文件,将路径/xx/test_cpp/mypkg/pkg
和/xx/test_cpp/mypkg/ns
加入到sys.path
中,就可以通过import nsp
导入文件夹了。
# /xx/test_cpp/mypkg/nsa/nsp/nsp1.py
def print_nsp1():
print(f"nsp1")
# /xx/test_cpp/mypkg/nsa/nsp/nsp2.py
def print_nsp2():
print(f"nsp2")
sys.path.insert(-1, "/xx/test_cpp/mypkg/nsa")
sys.path.insert(-1, "/xx/test_cpp/mypkg/nsb")
import nsp # 这里会将两个路径下的nsp合成一个
nsp.print_nsp1()
nsp.print_nsp2()
注意两个文件夹nsp
中都不能包含nsp.py
,否则无法导入另外一个。
使用 namespace package
命名包时,Python
包的搜寻规则为,先搜寻常规的package
,再搜寻module
文件,然后若搜索到不包含__init__.py
文件的目录即namespace package
会先记录其路径,待sys.path
路径搜寻完毕,若regular package
和module
中都没有而namespace package
中有,就导入namespace pakge
。
# 2.Python Package
工程
一个Python Package
工程目录示例:
packaging_tutorial/
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│ └── packaging_tutorial/
│ ├── __init__.py
│ └── example.py
└── tests/
tests
文件夹中放的是包的单元测试文件pyproject.toml
文件告诉前端打包工具如pip/build
等应该使用哪个后端工具如setuptools/ Hatchling
等创建工程的发布包。
[build-system]
# requires是构建包需要的依赖,不需要手动的安装,前端工具如`pip`等会自动安装
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
除了指定编译系统之外,还需要为包添加元数据metadata
,依赖dependencies
和内容contents
等,这些内容可以保存在pyproject.toml
中,也可单独写到setup.py
或者setup.cfg
文件中。
如‵setup.py`
from setuptools import setup
setup(
name='mypackage',
version='0.0.1',
install_requires=[
'requests',
'importlib-metadata; python_version == "3.8"',
],
)
或者setup.cfg
:
[metadata]
name = mypackage
version = 0.0.1
[options]
install_requires =
requests
importlib-metadata; python_version < "3.8"s
setup
方法中接受的参数以及含义介绍可以参考这里 (opens new window)
当setup.py/setup.cfg
和pyproject.toml
文件有重复配置的内容时,优先使用pyproject.toml
中的内容。
# 2.1 安装及打包并发布到pypi
- 开发模式安装本地包
pip install --editable .
- 打包包文件,执行完成后,在包路径下生成
dist
文件夹,其中有打包好的文件
python -m build
# dist
# ├── mypkgg-0.0.1-py3-none-any.whl
# └── mypkgg-0.0.1.tar.gz
- 将包上传到
pypi
# 1.先安装twine包
pip install twine
python3 -m twine upload --repository testpypi dist/*
输入pypi
的账号,密码后就可以将包上传到pypi
数据库中了,网站上访问显示如下:
- 如果
git
仓库是package
,还可以通过如下形式安装:
pip install -q git+https://github.com/lx-r/mypkg.git
在python
中,为了避免setup.py
的误执行,现在更推荐使用setup.cfg
。
上面包中的文件都是手动创建的,其实每个Python
包都需要包含上述文件,如此就可以使用模板来创建Python
包,常用的工具有cookiecutter
(opens new window)等。
# 2.2 将Python
文件编译成.so
需要使用cython
pip install cython
在setup.py
文件中设置
from Cython.Build import cythonize
setup(ext_modules = cythonize(["hello1.py", "hello2.py"]))
执行编译命令:
python setup.py build_ext
值得注意的是这里打包的.so
文件只能适用于同样平台的同样python
版本。
# 3.包的搜索路径
先搜寻built-in
模块module
,内建模块在变量sys.builtin_module_names
中。
然后到sys.path
路径中搜寻,sys.path
中包含以下部分,
- 当前路径
- 环境变量
PYTHONPATH
- 缺省的安装路径如
site-packages
关于包内的导入方式,当包中包含子包的时候,可以使用绝对路径导入,也可以通过相对路径的方式导入:
# mypkg
├── __init__.py
├── mypkg.py
└── utils
├── __init__.py
└── utils.py
导入方式:
from .utils import utils
# in utils.py
from ..mypkg import xx
值得注意的是,相对路径的引入方式都是基于当前模块的名称__name__
的,而在主模块中其__name__==__main__
,因此在主模块函数中必须使用绝对导入方式。
# 参考资料
- 1.https://docs.python.org/3/reference/import.html#packages (opens new window)
- 2.https://www.cnblogs.com/chaoguo1234/p/9350396.html (opens new window)
- 3.https://docs.python.org/3/tutorial/modules.html#the-module-search-path (opens new window)
- 4.https://setuptools.pypa.io/en/latest/userguide/quickstart.html (opens new window)
- 5.https://zhuanlan.zhihu.com/p/265462717 (opens new window)