そぬばこ

備忘録とか、多分そんな感じ。

高速かつ PEP 582 で仮想環境を捨てる Python パッケージマネージャ PDM を試す

この記事は Sansan Advent Calendar 2022 19日目の記事です。

前日は fujisyo32 さんの

zenn.dev

でした。

今年は特に画像周りで拡散モデルの話題で持ち切りでしたね。言語生成周りの研究も非常に興味深いです。

はじめに

私が所属する研究開発部では、Python のパッケージマネージャとして Poetry を標準的に利用しています。

github.com

Rust のように toml でパッケージを人間が認識しやすい形で管理できる点は非常に魅力的であり、setup.py, requirements.txt, setup.cfg, MANIFEST.in 等を代替できるため非常に便利です。 しかしながら最近、Poetry を用いたインストールやパッケージ追加等の依存解決に凄まじく時間を要しており、なんとか速度削減して開発のサイクルを早めることは出来ないかなと感じております。

そこで今回は、以前からあくまで個人的に試したいと思っていた高速なパッケージマネージャである PDM を試してみたいと思います。

PDM とは

公式のロゴ

PDM も Poetry と同様に pyproject.toml によってパッケージを管理する Python パッケージマネージャです。

github.com pdm.fming.dev

Poetry との大きな差異としては、次の2点が挙げられます。

  • PEP 582 に基づき、仮想環境を使わずに利用できる仕組みを搭載 (利用するかは選択可能)
  • 依存解決を含めて、動作が非常に高速

一方で CLI ツールとしては Poetry と操作感が非常に似ており Poetry ユーザであれば簡単に利用することが可能です。早速試していきましょう。

PDM を試す

紹介時の PDM のバージョンは 2.3.3 です。

インストール

インストール方法は公式HP等を参照してください。よくある curl で持ってくるやつです。

$ curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python
Installing PDM (2.3.3): Creating virtual environment
Installing PDM (2.3.3): Installing PDM and dependencies
Installing PDM (2.3.3): Making binary at /Users/nersonu/.local/bin
Usage: pdm [-h] [-V] [-c CONFIG] [-v] [-I] [--pep582 [SHELL]] {add,build,cache,completion,config,export,import,info,init,install,list,lock,publish,remove,run,search,self,plugin,show,sync,update,use,venv} ...

    ____  ____  __  ___
   / __ \/ __ \/  |/  /
  / /_/ / / / / /|_/ /
 / ____/ /_/ / /  / /
/_/   /_____/_/  /_/

Commands:
  {add,build,cache,completion,config,export,import,info,init,install,list,lock,publish,remove,run,search,self,plugin,show,sync,update,use,venv}
    add                 Add package(s) to pyproject.toml and install them
    build               Build artifacts for distribution
    cache               Control the caches of PDM
    completion          Generate completion scripts for the given shell
    config              Display the current configuration
    export              Export the locked packages set to other formats
    import              Import project metadata from other formats
    info                Show the project information
    init                Initialize a pyproject.toml for PDM
    install             Install dependencies from lock file
    list                List packages installed in the current working set
    lock                Resolve and lock dependencies
    publish             Build and publish the project to PyPI
    remove              Remove packages from pyproject.toml
    run                 Run commands or scripts with local packages loaded
    search              Search for PyPI packages
    self (plugin)       Manage the PDM program itself (previously known as plugin)
    show                Show the package information
    sync                Synchronize the current working set with lock file
    update              Update package(s) in pyproject.toml
    use                 Use the given python version or path as base interpreter
    venv                Virtualenv management

Options:
  -h, --help            show this help message and exit
  -V, --version         show the version and exit
  -c CONFIG, --config CONFIG
                        Specify another config file path(env var: PDM_CONFIG_FILE)
  -v, --verbose         -v for detailed output and -vv for more detailed
  -I, --ignore-python   Ignore the Python path saved in the .pdm.toml config
  --pep582 [SHELL]      Print the command line to be eval'd by the shell

Successfully installed: PDM (2.3.3) at /Users/nersonu/.local/bin/pdm

PDM でプロジェクトを立ち上げる

プロジェクトのルートディレクトリで pdm init すれば、対話式でプロジェクトを立ち上げることが出来ます。

$ pdm init
Creating a pyproject.toml for PDM...
Please enter the Python interpreter to use
0. /Users/nersonu/.pyenv/shims/python3 (3.10)
1. /Users/nersonu/.pyenv/shims/python (3.10)
2. /Users/nersonu/.pyenv/versions/3.10.9/bin/python3.10 (3.10)
3. /Users/nersonu/.pyenv/shims/python3.10 (3.10)
4. /Users/nersonu/.pyenv/shims/python3.9 (3.9)
5. /usr/local/bin/python3.9 (3.9)
6. /Users/nersonu/.pyenv/versions/3.9.13/bin/python3.9 (3.9)
7. /usr/bin/python3 (3.8)
8. /usr/bin/python2.7 (2.7)
9. /Users/nersonu/Library/Application Support/pdm/venv/bin/python (3.10)
Please select (0): 0
Using Python interpreter: /Users/nersonu/.pyenv/shims/python3 (3.10)
Would you like to create a virtualenv with /Users/nersonu/.pyenv/versions/3.10.9/bin/python3? [y/n] (y): y
Is the project a library that will be uploaded to PyPI [y/n] (n): n
License(SPDX name) (MIT):
Author name (nersonu):
Author email (nersonu@gmail.com):
Python requires('*' to allow any) (>=3.10): >=3.9
Changes are written to pyproject.toml.
$ ls
pyproject.toml

特徴的な点としては、利用する Python インタプリタを指定することです。 ここで指定したインタプリタは、.pdm.toml に書かれます。

[python]
path = "/Users/nersonu/.pyenv/shims/python3"

なお、ユーザごとに利用するインタプリタが異なることから、このファイルは git などで commit しないように気をつけましょう。

また、 Poetry や Rust の Cargo のように src ディレクトリや README ファイルは生成されません。

生成された pyproject.toml はこのようになっています。

[tool.pdm]

[project]
name = ""
version = ""
description = ""
authors = [
    {name = "nersonu", email = "nersonu@gmail.com"},
]
dependencies = []
requires-python = ">=3.9"
license = {text = "MIT"}

pyproject.toml の形式は PEP 621 に基づいており、Poetry とは若干形式が異っていますね。

仮想環境については、 pdm init の際に生成するようにした (以下)

Would you like to create a virtualenv with /Users/nersonu/.pyenv/versions/3.10.9/bin/python3? [y/n] (y): y

ため、pdm run でルートに作られた .venv を利用して実行することができます。

$ pdm run which python
/Users/aomi/workspace/try_pdm/init_pdm/.venv/bin/python

パッケージの追加

poetry add と同じような形で pdm add でパッケージの追加が可能です。

$ pdm add numpy pandas
Adding packages to default dependencies: numpy, pandas
🔒 Lock successful
Changes are written to pdm.lock.
Changes are written to pyproject.toml.
Synchronizing working set with lock file: 5 to add, 0 to update, 0 to remove

  ✔ Install six 1.16.0 successful
  ✔ Install python-dateutil 2.8.2 successful
  ✔ Install pytz 2022.7 successful
  ✔ Install pandas 1.5.2 successful
  ✔ Install numpy 1.23.5 successful

🎉 All complete!
$ ls
pdm.lock pyproject.toml

pdm.lock が生成され、poetry.lock と同様に依存関係等がここに残されます。

また、 pyproject.toml は以下のように更新されています。

[tool.pdm]

[project]
name = ""
version = ""
description = ""
authors = [
    {name = "nersonu", email = "nersonu@gmail.com"},
]
dependencies = [
    "numpy>=1.23.5",
    "pandas>=1.5.2",
]
requires-python = ">=3.9"
license = {text = "MIT"}

Poetry と同様に、開発用ライブラリとライブラリのグループ管理が可能です。 Poetry と若干異なるところは、開発用ライブラリをグループと同様に依存関係に含めるか、独立させるかオプションの指定の仕方で選べるところです。 今回は依存関係に含めない形で、開発用ライブラリとして pytest をインストールする形を紹介します。

$ pdm add -d pytest
Adding packages to dev dev-dependencies: pytest
🔒 Lock successful
Changes are written to pdm.lock.
Changes are written to pyproject.toml.
Synchronizing working set with lock file: 7 to add, 0 to update, 0 to remove

  ✔ Install exceptiongroup 1.0.4 successful
  ✔ Install iniconfig 1.1.1 successful
  ✔ Install tomli 2.0.1 successful
  ✔ Install pluggy 1.0.0 successful
  ✔ Install packaging 22.0 successful
  ✔ Install attrs 22.1.0 successful
  ✔ Install pytest 7.2.0 successful

🎉 All complete!

pyproject.toml では tool.pdm.dev-dependencies の項に追加されています。

[tool.pdm]
[tool.pdm.dev-dependencies]
dev = [
    "pytest>=7.2.0",
]

[project]
...

グループ追加 (ここでは "plot" という名前にしています) は以下のように行えます。

$ pdm add -G plot matplotlib seaborn
Adding packages to plot dependencies: matplotlib, seaborn
🔒 Lock successful
Changes are written to pdm.lock.
Changes are written to pyproject.toml.
Synchronizing working set with lock file: 13 to add, 0 to update, 0 to remove

  ✔ Install cycler 0.11.0 successful
  ✔ Install pyparsing 3.0.9 successful
  ✔ Install kiwisolver 1.4.4 successful
  ✔ Install contourpy 1.0.6 successful
  ✔ Install seaborn 0.12.1 successful
  ✔ Install fonttools 4.38.0 successful
  ✔ Install pillow 9.3.0 successful
  ✔ Install matplotlib 3.6.2 successful

🎉 All complete!

最終的な pyproject.toml は次のようになりました。

[tool.pdm]
[tool.pdm.dev-dependencies]
dev = [
    "pytest>=7.2.0",
]

[project]
name = ""
version = ""
description = ""
authors = [
    {name = "nersonu", email = "nersonu@gmail.com"},
]
dependencies = [
    "numpy>=1.23.5",
    "pandas>=1.5.2",
]
requires-python = ">=3.9"
license = {text = "MIT"}

[project.optional-dependencies]
plot = [
    "matplotlib>=3.6.2",
    "seaborn>=0.12.1",
]

なお、 project.optional-dependencies の中に dev を含めたい場合は -dG で出来るようです。

virtualenv との併用

pyenv local hoge のように、 pyenvpyenv-virtualenv を利用している場合の挙動を確認しておきましょう。

$ pyenv local try_pdm
$ pip -V
pip 22.3.1 from /Users/nersonu/.pyenv/versions/3.10.9/envs/try_pdm/lib/python3.10/site-packages/pip (python 3.10)

試しに pdm init してみると、仮想環境を生成する質問がなくなり、生成されなくなりました。

$ pdm init
Creating a pyproject.toml for PDM...
Please enter the Python interpreter to use
0. /Users/nersonu/.pyenv/shims/python3 (3.10)
1. /Users/nersonu/.pyenv/shims/python (3.10)
2. /Users/nersonu/.pyenv/versions/3.10.9/bin/python3.10 (3.10)
3. /Users/nersonu/.pyenv/shims/python3.10 (3.10)
4. /Users/nersonu/.pyenv/shims/python3.9 (3.9)
5. /usr/local/bin/python3.9 (3.9)
6. /Users/aomi/.pyenv/versions/3.9.13/bin/python3.9 (3.9)
7. /usr/bin/python3 (3.8)
8. /usr/bin/python2.7 (2.7)
9. /Users/nersonu/Library/Application Support/pdm/venv/bin/python (3.10)
Please select (0): Using Python interpreter: /Users/nersonu/.pyenv/shims/python3 (3.10)
Is the project a library that will be uploaded to PyPI [y/n] (n):
License(SPDX name) (MIT):
Author name (nersonu):
Author email (nersonu@gmail.com):
Python requires('*' to allow any) (>=3.10): >=3.9
Changes are written to pyproject.toml.

このまま pdm run でコマンド実行してみたところ、既に設定した仮想環境を利用できました。 既にプロジェクトで仮想環境が設定されている場合、認識して使ってくれるようですね。

PEP 582 の世界を体験する

PEP 582 とは

peps.python.org

__pypackages__ というディレクトリをルートに置いておき、ここに保存されているパッケージを使おうという考えです。 こうすることで、仮想環境作ってそこでライブラリを入れて……という一連の流れをスキップ出来ます。 PEP 582 に対応しているツールはほとんどなく、PDM はこれを実現している希少なソフトウェアと言えるかもしれません。早速体験してみます。

PDM で PEP 582 の機能を有効にする

公式ページには、 bash でのやり方が載っていますが、自分は zsh を使っているので適当に .zshrc に突っ込んでみます。

$ pdm --pep582 >> ~/.zshrc
$ exec $SHELL

プロジェクトを作ってみる

先に __pypackages__ ディレクトリを作っておきます。

$ mkdir __pypackages__

pdm init してみます。

pdm init
Creating a pyproject.toml for PDM...
Please enter the Python interpreter to use
0. /Users/nersonu/.pyenv/shims/python3 (3.10)
1. /Users/nersonu/.pyenv/shims/python (3.10)
2. /Users/nersonu/.pyenv/versions/3.10.9/bin/python3.10 (3.10)
3. /Users/nersonu/.pyenv/shims/python3.10 (3.10)
4. /Users/nersonu/.pyenv/shims/python3.9 (3.9)
5. /usr/local/bin/python3.9 (3.9)
6. /Users/nersonu/.pyenv/versions/3.9.13/bin/python3.9 (3.9)
7. /usr/bin/python3 (3.8)
8. /usr/bin/python2.7 (2.7)
9. /Users/nersonu/Library/Application Support/pdm/venv/bin/python (3.10)
Please select (0):
Using Python interpreter: /Users/nersonu/.pyenv/shims/python3 (3.10)
Would you like to create a virtualenv with /Users/nersonu/.pyenv/versions/3.10.9/bin/python3? [y/n] (y): n
You are using the PEP 582 mode, no virtualenv is created.
For more info, please visit https://peps.python.org/pep-0582/
Is the project a library that will be uploaded to PyPI [y/n] (n): n
License(SPDX name) (MIT):
Author name (nersonu):
Author email (nersonu@gmail.com):
Python requires('*' to allow any) (>=3.10):
Changes are written to pyproject.toml.

You are using the PEP 582 mode, no virtualenv is created.
For more info, please visit https://peps.python.org/pep-0582/

ちゃんと有効化されてそうです。試しに numpy でも追加してみましょう。

$ pdm add numpy
Adding packages to default dependencies: numpy
🔒 Lock successful
Changes are written to pdm.lock.
Changes are written to pyproject.toml.
Synchronizing working set with lock file: 1 to add, 0 to update, 0 to remove

  ✔ Install numpy 1.23.5 successful

🎉 All complete!
...

$ tree -L 3 __pypackages__
__pypackages__
└── 3.10
    ├── bin
    │   ├── f2py
    │   ├── f2py3
    │   └── f2py3.10
    ├── include
    └── lib
        ├── numpy
        └── numpy-1.23.5.dist-info

numpy が __pypackages__ 配下に入っていますね。面白い。

PEP 582 を実際に使用することはなかなか無いと思いますが、初学者にとって仮想環境に触れずに済むようなパッケージ管理は非常に魅力的です。 これを実現できる PDM なかなか良さげですね。

速度比較

前回の記事を流用します。実は、比較先の一番左が PDM でした。 nersonu.hatenablog.com

Python のパッケージマネージャの速度比較をしている Web ページから引用しています。画像は前2つは前回と同じです。

install 速すぎ 👍👍👍
Poetry とは比べ物にならない 👍👍

Poetry で一番ネックな部分である install 周りが超速いです。素晴らしいですね。

一方でパッケージの単純追加は Poetry に負けています (これだけ Poetry 1.3.1)。

add は Poetry に劣る
それにしても install が速いです。 Poetry を使っていると、コンテナをビルドする際の poetry install がとてつもなく時間がかかるため、こういった点は PDM に軍配が上がるんだろうなと感じています。

おわりに

今回は PDM を試しましたが、なかなか面白く、高速で良い感じでした。 いつか機会があれば業務でも導入してみたいですね。

明日は id:kur0cky さんです。皆様、よいクリスマスを。