光阴冢 赛博空间的自留地

使用 Poetry 管理 Python 项目

虽然后来 Python 写得越来越少,但是还是要和一些使用 Python 的项目打交道,并且发现许多同学对于 Python 的虚拟环境,包管理等问题并不是很熟悉,因此写这篇博客梳理一下,希望对其他人有所帮助。

TL;DR

  • 使用 pyenv 切换不同版本的 python
  • 使用 poetry 在不同 python 下管理项目依赖和虚拟环境
  • 绝不使用 sudo pip install 安装 python 包
  • 绝不使用 sudo make install 安装 python

Python 版本选择问题

除了特殊的本身就对于多版本软件有良好支持的发行版(例如 NixOS)以外,目前来说(2021 年)系统上安装的 python 最多只有两个大版本,2.x 和 3.x,这些 python 已经被系统包管理器处理好了各自的依赖关系,有时候甚至会用到系统关键组件中去。因此,除非知道自己在做什么,绝对不要使用 sudo pip install 或者 make install 来在全局安装 python 库 或者全新的 python 版本。

我所使用的多版本管理方案是 pyenv,能够在不同的 python 版本中自由切换,类似于 rbenv 或者 opam switch

如果你只想要 python3,对小版本号没有太苛刻的需求,那么没必要去使用 pyenv,可以在开发时直接使用后文提到的 poetry 来管理项目。

Poetry 和虚拟环境

在开发一个项目的时候,很可能需要安装许多软件包,这些软件包有可能还需要指定版本,因此与本地版本冲突,这时我们就需要虚拟环境进行隔离。虚拟环境是一个干净的 python 环境,在这个环境中进行 python 软件包的安装并不会影响其他虚拟环境或者系统环境, python3.3 之后通常使用自带的 venv 来实现。

在开发时,很常见的需求是需要复现环境,安装需要的软件包,通过 requirements.txt (原生 python)或者通过 Pipfile (pipenv)都可以,前者对于软件包的互相依赖没有很好的支持,后者的 lock 时间在项目稍微大一点的时候就令人发怵。

poetry 是一个更现代的 python 软件包管理器。

常见的使用方式

  1. 新建一个项目:poetry new <project-name>

  2. 在当前项目中添加依赖:poetry add <package-name>,加上 --dev 参数表示为开发依赖。

  3. 安装所有的依赖:poetry install

  4. 在虚拟环境中运行某个脚本或者程序:poetry run <cmd>。其中 <cmd> 可以是 python <filename>.py,也可以是一个存在于虚拟环境中的可执行文件的名字,例如 flaskfava 等等。

  5. 删除现有的虚拟环境:poetry env rm pytyon

  6. 如果需要使用国内镜像,则编辑 pyproject.toml,在 pyproject.toml 里加入

    1
    2
    3
    
    [[tool.poetry.source]]
    name = "zju"
    url = "https://mirrors.zju.edu.cn/pypi/web/simple"
    

安装个人使用的软件包

通过 pip install --user 能够为当前环境中 pip 所对应的 python 版本 在用户目录下安装软件包。如果对于个人所需要使用的软件包,同时系统包管理器又没有提供的,推荐在(使用 pyenv )选择了合适的 python 版本之后,用这种方式安装。

同时,需要将安装之后的 bin 路径添加到环境变量中,通常是 $HOME/.local/bin。添加后即可直接在命令行中使用了。

进阶使用

构建软件包并且指定可执行的脚本

执行 poetry build 即可构建软件包。

指定可执行程序的函数入口:

1
2
[tool.poetry.scripts]
abc = "bca.abc:main"

表示安装了最终构建的 wheel 包以后,有一个可执行文件叫做 abc,运行之后会执行 bca/abc.py 中的 main 函数的内容。

通过 Poetry 运行 Python 服务和脚本

有时候,系统没有相应的软件包可供安装(例如一些 GitHub 上的自动脚本),那么使用 poetry 来配置并运行也是一个不错的选择。

只需要建立一个工程,只使用 poetry add <...>poetry run <...> 即可,既不会影响本地的环境,也可以在迁移和升级的时候更方便。不需要编写任何代码,。

对于一些需要使用守护进程运行的服务,可以使用 systemd 管理。以下是我使用的一个 Unit file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[Unit]
Description=IUyhiuasd
After=network.target

[Service]
Type=simple
RemainAfterExit=true
WorkingDirectory=/usr/share/webapps/IUyhiuasd
ExecStart=/usr/bin/poetry run gunicorn wsgi:app
StandardOutput=journal

[Install]
WantedBy=multi-user.target