📦

Python パッケージング周辺の概念, 用語, ツールの整理

2023/11/07に公開

概要

Python のパッケージング (packaging) 周辺は歴史的経緯もあって様々なツールや用語が登場するためかなりややこしいです。
また、現在では非推奨 (deprecated) となっているツールの使用なども多くの記事で見られるため、何が現在推奨されるやりかたであるかを見極めるのが難しく感じられます。

この記事は2023年11月現在のパッケージング周辺のエコシステムを調べ、パッケージングを行うために推奨されていると思われる方法をまとめ、パッケージング周辺の用語の関係性を整理する試みです。

Python のパッケージングというのはかなり複雑かつ広いトピックであり、筆者自身もまだまだ理解しきれていない部分は多くあると感じています。
記事に間違いなどあれば指摘していただければ幸いです。

目的

  • Python パッケージングのおおまかな流れを理解する
  • パッケージの配布形態について理解する。
  • パッケージング周辺の用語やツールを整理し、各々の関連を理解できるようになる。

パッケージとは

ここでいうパッケージというのは、Python ライブラリの配布単位のことです。
日常的な例でいえば、たとえば pip install <package> のようにインストールを行う際の <package> の部分を指していると思えば良いでしょう。

言語機能としての import が認識する単位としての "パッケージ" とも密接に関係しますが、今回この意味での "パッケージ" については深く取り扱いません。

パッケージングのおおまかな流れ

パッケージングというのは、開発した Python コード (および C, C++, Rust などによる拡張) をパッケージとしてまとめて配布する行為のことを指します。

Python コードのパッケージングを行う際は、次のような手順を踏むことが必要です。

  1. 設定ファイルの記述: パッケージのメタデータやビルドの設定を記述したファイルを配置して、配布可能な単位 (distribution) をビルドするための準備をします。
  2. ビルド: 拡張モジュールのコンパイルを行ったり、メタデータを適切に配置することで配布物 (tar.gz や wheel) を生成します。
  3. アップロード: 作成したパッケージを誰でもインストールできるようにするためには、PyPI (Python package index) に配布物をアップロードします。

3 まで行えば、あとは誰でも pip install <package名> でインストールできる状態になります。

パッケージングの流れは以下にもう少し詳しくまとまっています。

https://packaging.python.org/en/latest/flow/

パッケージングの標準的な方法

以上でおおまかなパッケージングの流れを説明しましたが、ここからはパッケージングのもう少し具体的な方法や関連する標準を紹介してみたいと思います。

パッケージングの標準は PyPA (Python Packaging Authority) という組織が中心になって管理しています。
pipsetuptools といったパッケージングのためのツール類やパッケージ配布のためのプラットフォームである PyPI も PyPA が管理しているものです。
したがって、パッケージング周辺についての一次情報は PyPA の策定する仕様や関連する PEP (Python enhancement proposals) をあたることになります。

パッケージングを行う具体的な手順についての PyPA のチュートリアルは以下にあります。
Python パッケージを公開する予定がある人であれば一度はやっておくとよいと思いますが、周辺知識の説明は少なめです。
この記事では大まかな流れとしては PyPA のチュートリアルを参考にしつつ、周辺知識の部分を中心に補ってみようと思います。合わせて見ていただけると理解が深まるかと思います。

https://packaging.python.org/en/latest/tutorials/packaging-projects/

パッケージの設定ファイル

パッケージングを行うためには、まずはそのプロジェクトが何であるかというメタデータや配布物をどのようにビルドすればよいかという記述を行った設定ファイルが必要になります。
パッケージの設定を記述するための標準的なファイルは、PEP517, PEP518 で提案された pyproject.toml というものを利用することになっています。

pyproject.toml に記述する内容には以下のような情報が含まれます。

  • Build-backend の設定
  • メタデータ (パッケージ名, バージョン, ライセンスなど)
  • パッケージ依存関係
  • プロジェクトで利用するツールに関する設定 (tool.* テーブル)

具体的な記述内容については、先述の PyPA のチュートリアルやPyPA のサンプルプロジェクト などを見てみると良いでしょう。

Build-backend というのは実際にビルドを実行するためのツールのことで、たとえば setuptools, hatch, meson-python などがあります。
Build-backend というからには build-frontend もあるわけですが、これは pip や後述する build などのユーザーインターフェースに相当するツールにあたります。Build-frontend は内部的に build-backend の機能を呼び出すことでビルドを行っているわけです。
PEP517 は、build-frontend が適切に build-backend の機能を呼び出せるように標準的なインターフェースを定めています。

Build-backend としては setuptools が pyproject.toml が登場する以前は実質的に唯一の選択肢に近かったようですが、多様な build-backend を利用できるようにするために pyproject.toml が生まれたという背景もあるようです。

プロジェクトのビルド方法に関して、pyproject.toml は build-frontend に対してどの build-backend を使うかという情報を与えますが、build-backend が具体的にどのようにビルドを実行するかという点については backend の種類に応じて追加で設定が必要な場合があります。
このような build-backend 向けの設定は pyproject.toml 内の tool.<ツール名> のテーブル内に記載することもあれば、別ファイルを使用することもあります。
Build-backend 向けの設定を記述するファイルとしては例えば以下のようなものがあります。

Build-backend 設定ファイル
setuptools setup.py, setup.cfg
meson-python meson.build

パッケージのビルド

pyproject.toml と build-backend 用の設定が用意できたらいよいよ配布可能な distribution をビルドすることができます。
ビルドには PyPAの提供する build というツールを使うことができます。

python -m build

を pyproject.toml を配置したプロジェクトのトップレベルで実行すれば dist/ 以下に配布可能な distribution が生成されます。

ここで、配布可能な distribution には2種類あることを抑えておくと良いでしょう。

  • Source distribution (sdist): ビルド可能なソースコードを含んだ配布形態。ユーザー側でビルドする。
  • Built distribution (bdist): ビルド済みの配布形態。ユーザー側では unzip 等で展開するだけ。

上記の build ツールは source distribution, built ditribution (典型的には wheel) のどちらも生成することができます。

Source distribution は基本的にはソースディレクトリを tar.gz で固めたものだと思ってもらえればよいでしょう。Source distribution からインストールを行う場合にはユーザー側でビルドが行われるので C コンパイラや Python の C API 向けのヘッダファイル等はユーザー側で用意しておく必要があります。
ビルドは時間がかかる上、コンパイラ等が足りないとビルドができずエラーとなってしまうので、ユーザー側の負担は大きいといえるでしょう。

それに対してビルド済みの状態で配布される built distribution のフォーマットとして使われるのが wheel と呼ばれるものです。Wheel はもともと PEP427 で定義され、現在は PyPA の策定する仕様として管理されています。Wheel の導入の背景には、従来の source distribution に対してビルドをスキップできることによるインストールの高速化や、ユーザー側でビルド環境を用意しなくてよい利便性のためのねらいがあったようです。

Built distribution というと C などによる拡張モジュールを含むようなパッケージをイメージしがちですが、python コードのみのいわゆる pure python なパッケージでも wheel を含めて配布することが一般的です。Pure python なパッケージでも "ビルド" ステップでメタデータおよび依存関係の抽出やそのための一時的な環境の構築を行いますが、wheel によるインストールではその部分を省略できることでインストールが高速化されるようです。

C などによる拡張モジュールを含むようなパッケージの wheel は、バイナリ形式の共有ライブラリ等を含むためプラットフォーム依存になります。 バイナリファイルを含むような wheel は基本的に OS やマシンアーキテクチャが違うと互換性はなくなります。

パッケージのアップロード

配布可能な distribution が用意できたら、最後は PyPI にアップロードすればパッケージを誰もが利用できる状態になります。
アップロードのためには twine というツールを用いるのが一般的です。dist/ 以下に配布可能な distribution を作成している状態であれば、

twine upload dist/*

でアップロードできます。

過去のパッケージングツール

最後に、パッケージングのためにかつて使われていたが現在はほかのツールに置き換えられているものをいくつか簡単に紹介します。
主なねらいは、これらのツールが現在では適切な代替に置き換えられていることを理解し、

  • これらのツールの名前を聞いても混乱しない
  • どれを使えば良いのか迷わなくて済む

ようにすることです。

distutils

古くからある python パッケージのビルドツールであり、標準ライブラリに含まれる (3.12 で廃止)。
現在は setuptools が後継となっている。

https://docs.python.org/3.11/library/distutils.html

easy_install

パッケージのダウンロード, インストールを行う。
現在は pip install にとって代わられている。

https://setuptools.pypa.io/en/latest/deprecated/easy_install.html

egg

かつて setuptools の独自のバイナリフォーマットとして登場した。
現在は wheel にとって代わられている。

https://packaging.python.org/en/latest/discussions/wheel-vs-egg/

まとめ

この記事では python プロジェクトのパッケージングの流れや、その過程で登場する様々な用語について説明を行いました。

この記事を通して、python のパッケージングにおいて

  • パッケージングを行う際の pyproject.toml の記述, ビルド, アップロードという一連の流れ
  • ビルドにおける frontend と backend の分担
  • 配布物の種類 (sdist, bdit) とそれらがどのように利用されるか
  • パッケージング周辺で利用される各ツール類が全体のなかでどのような役割にあるものなのか

などの具体的なイメージが理解できるようになっていたら幸いです。

これらの知識は自作のパッケージをパッケージングするために必要なだけでなく、配布されているパッケージの1ユーザーとしてもインストール時のトラブルシューティングに役立つものと思います。

Further reading

パッケージングの標準を抑える上で、PyPA の User Guide は避けて通れないでしょう。
各種の仕様 (specification) についてもまとまっています。

https://packaging.python.org/en/latest/

以下の記事は、2019 年までのパッケージングまわりの開発の歴史や周辺の標準に関する情報がよくまとまっています。

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

以下の記事は、PyPI を中心としてパッケージングのエコシステムの全体像を概観しています。
また、最近のパッケージマネージャ (poetry, rye 等) に関する言及があります。

https://zenn.dev/zerebom/articles/60d5ad4b18c901

GitHubで編集を提案

Discussion