
PyPI使用指南

本文简单介绍了如何打包上传一个属于你自己的Python库,以及Python虚拟环境管理相关的一些问题
在Python包管理与分发的发展历程中,涌现了众多开发工具,主流的大致如下
- setuptools: 允许开发者定义如何构建、打包和安装他们的项目,广泛使用的老牌工具
- flit: 适合于不需要复杂构建过程的简单或单文件项目,简单易用
- Poetry: 提供了依赖管理和打包的集成解决方案
flit和Poetry都使用pyproject.toml进行配置,setuptools使用setup.py/pyproject.toml进行配置
关于一个优秀项目的像CI/CD,文档等等工作,本文不做详细介绍
xxxxxxxxxx11 1$ vim ~/.zshrc2ZSH_THEME=”powerlevel10k/powerlevel10k”3plugins=( git zsh-syntax-highlighting zsh-autosuggestions )4source ~/.zshrc5vim ~/.bashrc6alias ls=’lsd’7alias ll=’lsd -l’8alias la=’lsd -a’9alias lla=’lsd -la’10alias lt=’lsd –tree’11$ source ~/.bashrcbash
一些虚拟环境管理工具
- venv & virtualenv: 较古老的工具,功能也不够强大
- pipenv: 结合了pip和virtualenv,简化了依赖管理和虚拟环境的创建过程
- conda: 一个跨平台的包和环境管理器,还可以管理非Python库和依赖
包管理与分发
一般来说,一个Python包的项目结构应该看起来像下面这样
1 | example_package/ |
如果你想要文档或CI/CD的配置,你可以学习相关知识并使用在你的项目中
- .travis.yml: 如果您使用Travis CI或其他持续集成服务,您可能需要这样一个配置文件来定义如何运行测试和其他自动化任务
- .readthedocs.yml: 如果您使用Read the Docs来托管文档,这个文件将帮助配置文档的构建过程
setuptools
尽管pyproject.toml
正在逐渐成为替代setup.py
和setup.cfg
的新标准,还是建议了解一下后者的写法
setup.py
1 | from setuptools import setup, find_packages |
- name: 包的名字
- version: 包的版本(应遵循语义化版本控制)
- author: 包的作者/包的团队
- author_email: 作者/团队的邮箱
- description: 该包的一个简短描述
- long_description: 从README文件读取的长描述
- long_description_content_type: 长描述的格式
- url: 项目仓库的URL
- packages: 自动查找包含__init__.py的文件夹
- package_dir: 安装包时告诉setuptools应该从哪个目录下查找源代码
- classifiers: 分类信息,方便帮助用户找到您的包
- python_requires: Python版本要求
- install_requires: 项目依赖列表
- include_package_data: 包含在MANIFEST.in中的文件
上面最后提到了MANIFEST.in
文件,下面简要介绍一下这个
- include: 指定需要包含在分发包中的文件
- exclude: 指定需要排除的文件
- recursive-include: 递归地包含某个目录中的文件
- recursive-exclude: 递归地排除某个目录中的文件
- global-include: 全局包含匹配模式的文件
- global-exclude: 全局排除匹配模式的文件
- prune: 排除某个目录的所有内容
e.g.
1 | include LICENSE |
当您希望您的包在安装后提供一种(或多种)命令行工具供用户直接在终端中执行时,你可能需要配置entry_points
e.g.
1 | setup( |
如果你的项目只是一个或多个单文件模块(不包含子目录作为包),可以使用py_modules
来配置
e.g.
1 | setup( |
当然,如此简单的结构通常只是一个脚本项目,你可以搭配上面
entry_points
来使用
setup.cfg
setup.cfg
是一个配置文件,用于静态定义项目设置和选项,与setup.py
结合使用,使得setup.py
更简洁
e.g.
1 | [metadata] |
我个人更习惯写setup.py,最佳实践而言,使用
setup.cfg
更现代化,setup.py
可以确保向前兼容
对于上面在setup.py
中提到的entry_points
,在setup.cfg
中可做如下配置
e.g.
1 | [options] |
针对py_modules
,在setup.cfg
中做如下配置
e.g.
1 | [options] |
pyproject.toml
使用pyproject.toml
配置setuptools
是一个更现代的方法,提供了一种声明式的项目配置方式
1 | [build-system] |
[build-system] : 指定构建这个项目所需的工具和版本
[project] : 项目的基本元数据
[tool.setuptools]
find_namespace表示使用命名空间包,如果您的项目不使用命名空间包,改成
find:
命名控件包允许你将一个单独的Python包分散到多个目录中,这主要用于大型项目或在多个库之间共享代码的情况,命名空间包的特点是它们没有
__init__.py
文件在传统Python包中,一个包是一个目录,并且包含一个
__init__.py
文件,然而,在命名空间包中,包由多个目录组成,这些目录可以分布在文件系统的不同位置,而不是必须位于同一个父目录下命名空间包通常用于以下情况
- 大型项目或框架:将包分为多个子包,这些子包分布在不同的项目或目录中
- 代码共享:多个项目需要共享同一代码库的一部分
where指定包的源代码位置
[options]
[options.packages.find]
[options.entry_points] : 定义一个或多个入口点,通常用于创建命令行工具
[project.urls]
上面setup.py
中提到的py_modules
在pyproject.toml
中没有直接的支持,如果你需要的话,可以使用如下方法
使用setup.py
1 | from setuptools import setup |
这个
setup.py
可以与pyproject.toml
一起存在,pyproject.toml
负责定义构建系统和依赖,而setup.py
负责py_modules
使用setup.cfg
1 | [options] |
build
配置完毕后,在setup.py/setup.cfg
同级目录使用下面这行命令即可完成构建
1 | python setup.py sdist bdist_wheel |
构建完成后在dist/下有两个比较重要的文件
- sdist: 源码分发(.tar.gz文件),包含您项目的源代码以及所有必要的文件,如
MANIFEST.in
中指定的文件 - bdist_wheel: 轮式分发(.whl文件),是一种二进制分发格式,更易于安装且不需要运行构建脚本。这通常是推荐的分发方式,因为它更加现代化且具有更好的兼容性
tips: 在你构建前请确保你安装了setuptools
和 wheel
从Python 3.10开始,直接使用setup.py
的方式不再是推荐的打包方法,推荐的方法是使用build
工具
1 | pip install build |
这个也是
pyproject.toml
的最佳搭配
upload
构建完成后,推荐使用twine
工具来上传Python包到PyPI,下面是几个注意点
账号准备,检测包名是否冲突
安装twine,没安装的话请使用
pip install twine
安装上传命令
1
2twine upload --repository testpypi dist/* # 上传到TestPyPI先进行测试
twine upload dist/* # 正式上传检查
- 元数据检查,确保
setup.py
、pyproject.toml
或setup.cfg
中的元数据是最新且准确的 - 依赖项和兼容性检查
- 检查本地安装,上传后从PyPI安装上传的包以验证是否正确工作
- 元数据检查,确保
flit
flit
使用pyproject.toml
文件进行配置,专注于纯Python包,并且不需要复杂的构建系统这也就意味着
flit
非常适合于小型或中等规模的纯Python项目,特别是那些不包含编译扩展的项目
这里解释几个概念
纯Python包
一个纯Python包仅仅包含Python代码和其他非编译文件,这些文件在安装时不需要编译,可以直接由Python解释器运行
编译拓展
一些包包含编译拓展,编译拓展通常用C、C++或其他语言编写,然后编译成适合特定平台的二进制格式
编译扩展需要在目标机器上或为目标机器进行编译,这使得打包和分发过程更加复杂,setuptools
提供了工具和功能来处理这种类型的包,但flit
目前不支持这种用法
pyproject.toml
1 | [build-system] |
- [build-system] : 定义了构建系统所需的
flit
版本和构建后端 - [tool.flit.metadata] : 包含了包的元数据,如包名、作者、描述文件、所需 Python 版本、分类等
- module : 指定主模块或包的名称。这应该与您的项目结构中的顶级包或模块名称匹配
- requires : 列出了项目的运行时依赖
- [tool.flit.metadata.requires-extra] : 定义了可选的额外依赖,比如用于开发或测试的依赖
tips: flit
默认情况下会将所有文件视为包的一部分,除非在.gitignore
中指定排除,不需要 include_package_data
确保您的项目结构中有一个与module
指定的名称匹配的顶级模块或包
build & upload
pyproject.toml
配置好后,可以使用flit
来构建和发布包
build
1
flit build
将在
dist/
目录下生成.tar.gz
和.whl
文件upload
1
flit publish
Poetry
Poetry
是一个现代的Python包管理和打包工具,它旨在简化包的依赖管理和发布过程;它集成了依赖管理、打包、发布等功能,通过提供一致的命令和工作流来改善 Python 包的开发体验
1 | [tool.poetry] |
pyproject.toml
写法和之前的类似,只是稍作更改即可
可通过下面两种方式安装
Poetry
1
2 curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
1
2 pip install poetry
重点介绍使用poetry
用到的几个命令
初始化新项目
1 | poetry new my_proj |
添加依赖
1 | poetry add package_name # 添加常规依赖 |
tips: 在运行poetry add
之前,确保您位于项目的根目录中,这样Poetry才能找到正确的pyproject.toml
文件
poetry.lock
文件是在您使用poetry add
添加第一个依赖后由Poetry自动创建的,并且确实会随着项目依赖的变化自动更新,这个文件的主要目的是确保项目依赖的一致性
安装所有依赖
1 | poetry install |
执行poetry install
命令时,Poetry
会根据poetry.lock
文件中锁定的版本来安装依赖,而不是依赖于 pyproject.toml
中的声明
定期运行poetry update
以更新依赖,并在进行充分测试后提交新的poetry.lock
文件,以保持依赖的更新
构建和发布包
1 | poetry build # 将在项目的 dist/ 目录下创建包文件 |
1 | poetry publish |
在将包发布到PyPI之前,您可能想先将其发布到TestPyPI进行测试,这有助于确保包的构建和发布过程按预期工作,使用 poetry publish --repository testpypi
来做到这一点
运行脚本
1 | poetry run python my_script.py |
poetry run
命令是Poetry
工具提供的一种方便的方式,用于在项目的虚拟环境中运行指定的命令;当您使用poetry run
执行命令时,Poetry
会确保该命令在项目的虚拟环境中执行,而不是在您的全局 Python 环境中,这意味着该命令将在一个包含您项目依赖的隔离环境中运行
poetry run
不仅限于运行Python脚本,它可以用来运行项目环境中的任何命令
e.g.
1 | poetry run pytest # 运行测试 |
PyPI
在你上传到PyPI时,需要验证登录,推荐使用token登录
在你注册完账号后,需要双重验证才允许你生成token(好像是因为太多恶意包上传到PyPI上了,为了安全考虑),在手机上下载一个google authenticator 即可,具体操作可以自行谷歌
生成token后,在上传时就可以输入__token__
作为用户名,使用token作为密码就可以了
为了省事,你也可以使用配置文件,命名为.pypirc
1 | [pypi] |
可以为不同的项目创建不同的 token,并限制它们的权限,以提高安全性
版本号
软件版本号是一种标识软件特定版本的系统,它用于追踪和区分软件的不同更新或改进。版本号通常包含数字和点(.
),有时也包括字母或其他字符。不同的项目或组织可能采用不同的版本命名规则,但以下是一些常见的元素和原则
常见版本号
- 主版本号:通常是第一个数字,表示大的版本更新,可能包含重大更改或新功能。例如,在版本
2.3.5
中,2
是主版本号 - 次版本号:紧跟在主版本号后面,通常表示较小的功能更新或改进。在
2.3.5
中,3
是次版本号 - 修补号/补丁号:通常是第三个数字,用于小的错误修复或安全补丁。在
2.3.5
中,5
是修订号 - 预发布标识:有时版本号后会跟有额外的标识,如
alpha
、beta
、RC
(候选发布版本)等,表示这是一个尚未完全稳定的版本。例如,3.0.0-alpha
- 构建号:在一些情况下,可能还会包括构建号,通常表示编译次数或日期。例如,
2.3.5.20210321
可能表示 2021 年 3 月 21 日的构建
版本号的作用
- 追踪变化:帮助用户和开发者了解软件的更新历史和当前状态
- 兼容性:指出不同版本之间的兼容性,特别是在软件库或API中,版本号可以帮助开发者判断不同版本的兼容性和依赖性
- 发布跟踪:在软件开发的生命周期管理中,版本号是区分不同阶段的关键,如开发阶段、测试阶段和发布阶段
- 错误跟踪:当用户报告问题时,了解他们使用的具体软件版本对于快速定位和解决问题至关重要
常见版本号命名规则
- 语义化版本控制:这是一种流行的版本命名规则,强调版本号应该传达关于底层代码更改的含义。例如,在SemVer中,主版本号的变更表示可能存在破坏向后兼容性的更改
- 日期-时间版本命名:一些项目使用日期或时间作为版本号,这在持续集成/持续部署(CI/CD)的环境中较为常见
- 阶段性版本命名:如
alpha
、beta
、RC
等,表示软件开发的不同阶段
虚拟环境
Python虚拟环境是一种工具,用于在隔离的环境中安装和管理Python包。它们允许您为每个项目创建独立的环境,这样不同项目的依赖就不会相互冲突,这对于管理项目依赖、保持一致的开发环境以及避免全局安装包引起的问题至关重要
为什么使用虚拟环境
- 依赖隔离:每个项目可以有自己的依赖,不同项目间的依赖不会相互干扰
- 版本控制:不同的项目可能需要相同包的不同版本,虚拟环境允许每个项目使用所需的特定版本
- 避免污染全局环境:在没有虚拟环境的情况下,所有包都会被安装到全局Python环境中,可能导致依赖混乱和版本冲突
- 实验和测试:为新工具或库创建一个独立的虚拟环境,而不会影响其他项目
pip
pip配置文件
全局配置
- Unix & macOS:
/etc/pip.conf
- Windows:在
%PROGRAMDATA%\pip\pip.ini
orC:\ProgramData\pip\pip.ini
用户级配置
- Unix & macOS:
$HOME/.config/pip/pip.conf
or~/.pip/pip.conf
- Windows:
%APPDATA%\pip\pip.ini
在虚拟环境下的配置,一般是在
<venv>/pip.conf
or<venv>/pip.in
也可以使用
pip config set xxxx
来配置
配置文件格式
e.g.
1 | [global] |
具体遇到什么配置,可以去互联网上搜索
常用命令
安装包
1
2
3pip install package_name
pip install package_name==version # 安装特定版本
pip install package_name.whl # 直接从.whl文件安装包卸载包
1
pip uninstall package_name
列出已安装包
1
pip list
显示包信息
1
pip show package_name
安装requirements.txt中的包
1
pip install -r requirements.txt
生成requirements.txt文件
1
pip freeze > requirements.txt
更新包
1
pip install --upgrade package_name
配置源(推荐写在配置文件中)
1
pip install package_name -i https://pypi.tuna.tsinghua.edu.cn/simple
查看配置
1
pip config list
venv & virtualenv
venv
venv
是Python 3.3及以上版本的标准库的一部分,因此无需额外安装即可使用,且轻量级
创建虚拟环境
1 | python3 -m venv /path/to/new/virtual/environment |
激活虚拟环境
Unix & MacOS
1
source /path/to/new/virtual/environment/bin/activate
Windows
1
\path\to\new\virtual\environment\Scripts\activate.bat
退出虚拟环境
1 | deactivate |
删除虚拟环境
1 | rm -rf /path/to/new/virtual/environment |
virtualenv
virtualenv
比venv
更加强大且灵活,并且支持Python 2
安装
1 | pip install virtualenv |
创建虚拟环境
1 | virtualenv /path/to/new/virtual/environment |
也可指定Python解释器的版本
1 | virtualenv -p python3.8 env |
激活虚拟环境
Unix & MacOs
1
source /path/to/new/virtual/environment/bin/activate
Windows
cmd
1
\path\to\new\virtual\environment\Scripts\activate.bat
PowerShell
1
\path\to\new\virtual\environment\Scripts\Activate.ps1
退出虚拟环境
1 | deactivate |
删除虚拟环境
1 | rm -rf /path/to/new/virtual/environment |
Pipenv
Pipenv
是一个Python开发工作流的工具,旨在将pip
的包管理和venv
的虚拟环境管理结合起来;它自动创建并管理一个虚拟环境,同时在项目的Pipfile
中添加/删除项目依赖,从而使开发过程更加简化
安装
1 | pip install pipenv |
创建新项目
1 | mkdir myproject |
安装和管理依赖
1 | pipenv install requests # 安装依赖 |
如果你想添加一个仅在开发中需要的包
1 | pipenv install pytest --dev |
安装所有依赖,根据 Pipfile
1 | pipenv install |
激活 & 退出虚拟环境
1 | pipenv shell |
Pipfile
查看依赖图
1 | pipenv graph |
查看项目的依赖图,这有助于理解依赖之间的关系
清理不需要的依赖
1 | pipenv clean |
卸载不在
Pipfile中声明的包
锁定依赖
1 | pipenv lock |
确保您的生产环境中使用的依赖与您的开发环境完全一致
Pipfile的基本结构
e.g.
1 | [[source]] |
通常,不需要手动创建或编辑
Pipfile
,当您使用Pipenv
安装或卸载包时,Pipenv
会自动为您创建和更新Pipfile
和Pipfile.lock
tips: Pipenv
本身并不支持直接从.whl文件来安装包,可以使用pip
安装后同步到Pipfile
- 使用
pip
安装.whl文件 - 使用
pipenv lock
来更新Pipfile.lock
- 使用
pipenv sync
来同步
反正就是不推荐在使用Pipenv
时安装不受信赖的包
Conda/Miniconda
在linux中,一般都会有一个系统层面的python版本,如果随意使用pip安装各种奇怪的库,可能会导致在滚动更新时的各种Python包问题,在日常开发中使用虚拟环境就很有必要,本文推荐使用miniconda来管理各个版本的Python以及包
Miniconda
安装
arch linux
1
yay -S miniconda3
从脚本安装(从官网下载Linux安装脚本后)
1
bash Miniconda3-latest-Linux-x86_64.sh
使用
创建新的Conda环境
1 | conda create --name py39 python=3.9 |
激活Conda环境
1 | conda activate py39 |
管理Conda环境
列出所有Conda环境
1
conda env list
删除环境
1
conda env remove --name py39
更新Conda及Python环境
更新Conda
1
conda update conda
更新某环境包
1
conda update --all --name py39
一个使用pipenv
+ 三种包构建分发工具的小例子 PyPI-demo