📦

Pythonのパッケージングと配布の全体像

2023/06/15に公開

EDIT: このブログと似た内容の話をPyCon APAC2023にてお話ししました。
こちらの登壇資料も合わせてご覧いただけると幸いです
https://speakerdeck.com/zerebom/pythondenopatukezingu-ekosisutemunoli-jie-toxian-chang-denohuo-yong-pycon-apac2023


こんにちはWantedlyの樋口です。

Pythonのパッケージングと配布は歴史が長く、多くのツール(ex. conda, pip, pipenv, poetry, rye...)が開発されてきました。これらの多様性はPythonが多くの人に使われ、継続的に改善されたゆえの賜物ですが、同時にこれらの理解を難しくしている要因にもなっていると感じます。

そこで本記事では、Pythonのパッケージングと配布の全体像を紹介します。パッケージングと配布が何か、なぜ重要なのか、そしてそれぞれのツールが何を解決しようとしているのかについて説明します。以下のような疑問を解決できることを想定しています。

  • パッケージングと配布の仕組みがなぜあるのか
  • 多数あるツールが何を解決するためのものなのか
  • パッケージングと配布について調べてたときに出てくる各用語の意味

⚠️注意事項:本記事における情報は私が調査し、なるべくソースを参照していますが、解釈や表現におかしい部分があれば、ぜひコメントでご指摘いただけますと幸いです。🙏

パッケージングと配布のエコシステムがある意義

そもそも、なぜPythonはPyPI(後述)などのパッケージのプラットフォームやそれにまつわるルール(PEPs(後述))など、パッケージと配布のエコシステムを持っているのでしょうか。それは多くの人がパッケージという巨人の肩に簡単に乗れるようにするためと言えます。

パッケージ利用者の視点から見ると、インストール先のOS、パッケージ間の依存関係、Pythonバージョン、プロジェクトごとの環境(仮想環境)など、様々な変数がある中で、使いたいパッケージを正確に、高速に、安全に、かつ簡単にインストールしたいと考えるはずです。

一方、パッケージ開発者の視点から見ると、上記のような様々な変数を持つ多くの利用者に対して、画一的で簡単な方法で自分のパッケージを配布し、使ってもらいたいと思うでしょう。

このような要求を満たすためには、パッケージを一元管理するプラットフォームと、パッケージのアップロード・ダウンロードの手順を規定するルールが必要となります。これがPythonのパッケージングと配布のエコシステムの存在意義です。

パッケージングと配布の全体像

次にブログの前提知識として、大まかに図を使って Python のパッケージングと配布の全体像を紹介します。

Pythonのパッケージングと配布のエコシステムは、Pythonソフトウェア財団(PSF)Python Packaging Authority (PyPA)が策定・運営している標準の元に構成されています。中心となるのは、Python Package Index (PyPI)というPythonのパッケージを管理するプラットフォームです。

このプラットフォームのもとでパッケージ開発者は、自分が作ったソースコードを他の人の環境でも呼び出し・実行可能にするために「パッケージング」という変換処理を行い、それをPyPIにアップロードします。

一方、パッケージ利用者はpipやpoetryなどのツールを使ってPyPIからパッケージをダウンロードし、自身のプロジェクトにインストールします。

このパッケージのアップロードやダウンロードの手順はPyPAが管理しているPython Enhancement Proposals (PEPs)によって規定されています。

⚠️一部の特殊なライブラリ(ex. cudf)はビルドやパッケージング方法が異なるためPyPI経由ではインストールを行いません。

パッケージングと配布を支えるサブタスクと関連するツールの解説

次に、パッケージングと配布のエコシステムを支えるための各コンポーネントに着目してより詳しく紹介します。

パッケージングと配布の標準化

パッケージ開発者/利用者ともに、統一的な操作や実装で多くのパッケージを利用できるように、PyPAが作成したPEPsを元にパッケージングと配布の標準化がされています。

PyPAによって策定されたパッケージングと配布のルールとして特に重要なルールはPEP 517とPEP 518にまとめられています。これらのPEPは、パッケージのビルドプロセスを標準化し、独自のツールを使ってビルドすることを可能にしました。

PEP 517 -- A build-system independent format for source trees
PEP 517は、Pythonのソースツリーに対するビルドシステム独立のフォーマットを定義します。つまり、ソースコードをパッケージングする際に setuptools だけでなく、他のビルドツールも使用できるようになりました。また、ビルド時のフロントエンド(ex. pipなどのパッケージインストーラー)とバックエンド(ビルドシステム)間のインターフェースを標準化しています。

PEP 518 -- Specifying Minimum Build System Requirements for Python Projects
PEP 518では、Pythonプロジェクトのビルドシステムに必要な最小限の要件を指定する方法を定義しました。これにより、プロジェクトがどのようなツールを必要とするかを明示的に指定でき、それらの依存関係を自動的にインストールすることが可能になりました。このPEPでpyproject.tomlが提案されています。

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

パッケージング

"パッケージング"はパッケージが多くの利用者にとって簡単にインストールできるように、利用されているリソースや設定などを再利用・配布可能な形式にまとめることを指します。

パッケージの利用者としてもパッケージングの仕組みや出てくる単語を理解すると、インストール時のエラーに対処する際に役立つと思います

PyPIにアップロードされるパッケージはsetup.pyやpyproject.tomlに記載されている設定を参照し、パッケージングツール(setuptools、distutils、wheel、flit、poetryなど)を用いてパッケージングされます。


Pythonのパッケージングの形式にはソースコードをそのまま配布する形式の「ソースディストリビューション(sdist)(ex. .targzや.zip)」とあらかじめビルドした「ビルドディストリビューション (bdist) (ex. .whl)」があります。

sdistを使うと、利用者は自分の環境に合わせてパッケージをビルドできる一方、bdistを使うと、利用者はビルド済みのパッケージを使ってインストールすることができます。ビルド済みのパッケージを使うことで、C言語などを他の言語を利用しているパッケージにおいて、コンパイラなどの開発ツールを必要とせず、かつ高速にインストールできますが、自身の環境に合わせてビルドされている必要があります。

例えば、numpyのPyPIのページを見ると、単一のsdistと環境に応じた複数のbdistの両方が配布されていることがわかります。

なお、Pytorchやcudfのような一部の機械学習パッケージが環境に応じてインストール方法が違う(pipにパラメータを渡す)のは、それらがPython以外の言語(例えば、C/C++やCUDA)で書かれた部分を含んでいるからです。

多くの場合、これらのパッケージはビルド済みの形で提供されており、直接pipなどを用いてインストールすることが可能ですが、一部の特殊な設定を必要とする場合には、手動でのビルドが必要となることがあります。

利用パッケージのインストールと依存関係解決

利用するパッケージをインストールするには、pipやpoetryなどのパッケージインストーラーを使います。

現在のパッケージインストーラー(pip,poetry)はソースを明示的に指定しなければ、PyPIを検索し、環境に合うbdistがあれば、bdistを利用し、なければsdistをビルドしてインストールするという流れを踏みます。

https://dev.to/icncsx/python-packaging-sdist-vs-bdist-5ekb

また、複数のパッケージをインストールする際には、それらのパッケージの依存関係を解決する必要があります。

依存解決は一般的に非常に難しい問題(ex. ダイヤモンド依存問題)です。PipenvやPoetryなどのパッケージインストーラーは、依存解決を行うためのアルゴリズムを持っており、特にPoetryは、高速にロバストに依存関係の解決を実現するため、2023年現在も継続的に改善されています。

https://python-poetry.org/blog/category/releases/

依存関係のリゾルバについては下記サイトにて詳しく述べられています。

https://vaaaaaanquish.hatenablog.com/entry/2021/03/29/221715

プロジェクトごとの環境の用意

あるマシンの中で複数のPythonプロジェクトを管理するためには、それぞれのプロジェクトごとに環境を分離する必要があります。これを実現するためには、仮想環境を用います。

仮想環境を作るにはvenvや、virtualenvなど専用のツールを使っても良いですし、比較的新しいパッケージインストーラー(Pipenv, Poetry, rye)には、仮想環境を作成する機能が内包されています。

またプロジェクトごとに、パッケージだけでなく、Pythonのバージョンを変えたいケースもあると思います。こういったケースでは、pyenvを使うことで、プロジェクトごとにPythonのバージョンを切り替えることができます。

パッケージングと配布を支えるツールの歴史

最後に、Python の主要なパッケージ管理ツールの時系列とその目的を説明します。各ツールはその時点でのベストプラクティスを反映しており、前のツールが抱えていた課題を解決することを目指しています。

  1. setuptools (2004年頃): Pythonのパッケージングと配布を初めて容易にするツールとして登場しました。これにより、Pythonコードをパッケージ化し、依存関係を管理する機能が提供されました。また、easy_installというパッケージインストールツールも提供されましたが、このツールは後にpipに取って代わられました。

  2. pip (2008年頃): Pythonのパッケージインストールを簡単にするためのツールとして登場しました。pipeasy_installに比べて使いやすさと機能性が向上しており、特にパッケージのアンインストールや特定のバージョンのパッケージのインストールなどが可能になりました。また、requirements.txtファイルを介してパッケージ依存関係を管理する方法も導入されました。

  3. pipenv (2017年頃): pipenvはPythonのパッケージ管理と仮想環境管理を統合するツールとして登場しました。pipvirtualenvのベストプラクティスを組み合わせて、より洗練されたワークフローを提供します。PipfilePipfile.lockを導入し、より詳細で直感的な依存関係管理を可能にしました。

  4. poetry (2018年頃): poetryはPythonのパッケージの依存関係管理とパッケージング/配布のための一元化されたツールを提供します。pyproject.tomlファイルを使用してパッケージのメタデータと依存関係を管理し、パッケージのビルド、公開、インストールのプロセスを効率化します。また、PEP517にも準拠するようになり,setuptoolsに依存しない選択肢も取れるようになりました。

  5. rye (2023年頃): ryeはパッケージの依存関係解決、依存関係のマルチバージョニング、仮想環境管理、幅広い機能を提供するツールです。また、パッケージングやインストールに限らず、Pythonのコードフォーマッターやリンターを内蔵し、Pythonプロジェクトのレイアウトを統一することを目指しています。

下記に代表的な機能の有無をまとめた表を示します。

ツール パッケージ作成 依存関係管理 パッケージインストール バージョン管理 仮想環境管理
setuptools 〇 (easy_install)
pip
pipenv
poetry
rye

このように歴史を見ると、Pythonのパッケージ管理ツールは、依存関係や仮想環境の管理などパッケージングと配布に必要な機能の解決を単一のツールで解決出来るように、徐々に進化してきたと言えそうです。

自分がハマった問題

下記の章では、自分がインストール周りでハマった問題とその原因をまとめます。ハマるたびに事例を追加していこうと思います。

Poetry v1.4でfasttextがインストールできない

Poetry v1.4 以降ではfasttextをインストールしようとするとbuild時にエラーが起きします。

https://github.com/facebookresearch/fastText/issues/1223

これはPoetry v1.4以降はデフォルトで、PEP517に従ってパッケージのインストールを試みるためです。つまり、sdistをbuildする際にインストール対象のパッケージのsetup.pyではなく、pyproject.tomlの設定を参照してビルドを行います。

このとき、インストールするパッケージがPEP517に対応していない(pyproject.tomlがなくsetup.pyしか持っていない場合)にはエラーが起きてしまいます。

この問題はpoetryの設定で、generate-setup-file = trueを指定し、setup.pyのスタブを用意する、もしくはbdistを手元に手動でダウンロードすることで解消されます。またはパッケージ側でPEP517に対応することで解消されます。

https://python-poetry.org/blog/announcing-poetry-1.4.0/#generate-setup-file--false

まとめ

本記事ではパッケージングと配布の全体像、具体的には、複数の役割の異なる組織や開発者の関係性や、エコシステムに必要な各機能や、パッケージングツールの開発の歴史をまとめました。

本来、bdistやsdistなどのパッケージングに関する単語はインストール時は意識しないものですが、このようなバックエンドとなる技術まで理解しておくといざバグが起きたときに見通しが付きやすいと思うので、参考にしていただければ幸いです。

今回は力尽きてしまいましたが、今後は既存のツールと新しいパッケージインストーラーのryeなどの比較なども書けたらと思います。

今後もPython周りや、推薦システム・機械学習について継続的に発信を行っていく予定なので、よければTwitterをフォローしてもらえると嬉しいです!

https://twitter.com/zerebom_3

参考文献

https://packaging.python.org/ja/latest/overview/

https://blog.recruit.co.jp/rls/2019-12-25-python-packaging-specs/

https://python-packaging-user-guide-ja.readthedocs.io/ja/latest/index.html

https://www.m3tech.blog/entry/python-packaging

Discussion